fix: add 2fa and email verification grace period (#10771)
parent
28b94a865f
commit
0f2b6bbe1a
|
@ -1,10 +1,10 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { authenticator } from "otplib";
|
||||
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
import { totpAuthenticatorCheck } from "@calcom/lib/totp";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/client";
|
||||
|
||||
|
@ -69,7 +69,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
}
|
||||
|
||||
// If user has 2fa enabled, check if body.code is correct
|
||||
const isValidToken = authenticator.check(req.body.code, secret);
|
||||
const isValidToken = totpAuthenticatorCheck(req.body.code, secret);
|
||||
if (!isValidToken) {
|
||||
return res.status(400).json({ error: ErrorCode.IncorrectTwoFactorCode });
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { authenticator } from "otplib";
|
||||
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
import { totpAuthenticatorCheck } from "@calcom/lib/totp";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
@ -48,7 +48,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
return res.status(500).json({ error: ErrorCode.InternalServerError });
|
||||
}
|
||||
|
||||
const isValidToken = authenticator.check(req.body.code, secret);
|
||||
const isValidToken = totpAuthenticatorCheck(req.body.code, secret);
|
||||
if (!isValidToken) {
|
||||
return res.status(400).json({ error: ErrorCode.IncorrectTwoFactorCode });
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { expect } from "@playwright/test";
|
|||
import { authenticator } from "otplib";
|
||||
|
||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
import { totpAuthenticatorCheck } from "@calcom/lib/totp";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
|
||||
|
@ -141,7 +142,7 @@ test.describe("2FA Tests", async () => {
|
|||
|
||||
async function fillOtp({ page, secret, noRetry }: { page: Page; secret: string; noRetry?: boolean }) {
|
||||
let token = authenticator.generate(secret);
|
||||
if (!noRetry && !authenticator.check(token, secret)) {
|
||||
if (!noRetry && !totpAuthenticatorCheck(token, secret)) {
|
||||
console.log("Token expired, Renerating.");
|
||||
// Maybe token was just about to expire, try again just once more
|
||||
token = authenticator.generate(secret);
|
||||
|
|
|
@ -148,7 +148,10 @@ const providers: Provider[] = [
|
|||
throw new Error(ErrorCode.InternalServerError);
|
||||
}
|
||||
|
||||
const isValidToken = (await import("otplib")).authenticator.check(credentials.totpCode, secret);
|
||||
const isValidToken = (await import("@calcom/lib/totp")).totpAuthenticatorCheck(
|
||||
credentials.totpCode,
|
||||
secret
|
||||
);
|
||||
if (!isValidToken) {
|
||||
throw new Error(ErrorCode.IncorrectTwoFactorCode);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { Authenticator, TOTP } from "@otplib/core";
|
||||
import type { AuthenticatorOptions } from "@otplib/core/authenticator";
|
||||
import type { TOTPOptions } from "@otplib/core/totp";
|
||||
import { createDigest, createRandomBytes } from "@otplib/plugin-crypto";
|
||||
import { keyDecoder, keyEncoder } from "@otplib/plugin-thirty-two";
|
||||
|
||||
/**
|
||||
* Checks the validity of a TOTP token using a base32-encoded secret.
|
||||
*
|
||||
* @param token - The token.
|
||||
* @param secret - The base32-encoded shared secret.
|
||||
* @param opts - The AuthenticatorOptions object.
|
||||
* @param opts.window - The amount of past and future tokens considered valid. Either a single value or array of `[past, future]`. Default: `[1, 0]`
|
||||
*/
|
||||
export const totpAuthenticatorCheck = (
|
||||
token: string,
|
||||
secret: string,
|
||||
opts: Partial<AuthenticatorOptions> = {}
|
||||
) => {
|
||||
const { window = [1, 0], ...rest } = opts;
|
||||
const authenticator = new Authenticator({
|
||||
createDigest,
|
||||
createRandomBytes,
|
||||
keyDecoder,
|
||||
keyEncoder,
|
||||
window,
|
||||
...rest,
|
||||
});
|
||||
return authenticator.check(token, secret);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks the validity of a TOTP token using a raw secret.
|
||||
*
|
||||
* @param token - The token.
|
||||
* @param secret - The raw hex-encoded shared secret.
|
||||
* @param opts - The TOTPOptions object.
|
||||
* @param opts.window - The amount of past and future tokens considered valid. Either a single value or array of `[past, future]`. Default: `[1, 0]`
|
||||
*/
|
||||
export const totpRawCheck = (token: string, secret: string, opts: Partial<TOTPOptions> = {}) => {
|
||||
const { window = [1, 0], ...rest } = opts;
|
||||
const authenticator = new TOTP({
|
||||
createDigest,
|
||||
window,
|
||||
...rest,
|
||||
});
|
||||
return authenticator.check(token, secret);
|
||||
};
|
|
@ -1,10 +1,9 @@
|
|||
import { authenticator } from "otplib";
|
||||
|
||||
import { deleteStripeCustomer } from "@calcom/app-store/stripepayment/lib/customer";
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||
import { deleteWebUser as syncServicesDeleteWebUser } from "@calcom/lib/sync/SyncServiceManager";
|
||||
import { totpAuthenticatorCheck } from "@calcom/lib/totp";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
@ -66,7 +65,7 @@ export const deleteMeHandler = async ({ ctx, input }: DeleteMeOptions) => {
|
|||
}
|
||||
|
||||
// If user has 2fa enabled, check if input.totpCode is correct
|
||||
const isValidToken = authenticator.check(input.totpCode, secret);
|
||||
const isValidToken = totpAuthenticatorCheck(input.totpCode, secret);
|
||||
if (!isValidToken) {
|
||||
throw new Error(ErrorCode.IncorrectTwoFactorCode);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createHash } from "crypto";
|
||||
import { totp } from "otplib";
|
||||
|
||||
import { totpRawCheck } from "@calcom/lib/totp";
|
||||
import type { ZVerifyCodeInputSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
@ -18,8 +18,7 @@ export const verifyCodeUnAuthenticatedHandler = async ({ input }: VerifyTokenOpt
|
|||
.update(email + process.env.CALENDSO_ENCRYPTION_KEY)
|
||||
.digest("hex");
|
||||
|
||||
totp.options = { step: 900 };
|
||||
const isValidToken = totp.check(code, secret);
|
||||
const isValidToken = totpRawCheck(code, secret, { step: 900 });
|
||||
|
||||
if (!isValidToken) throw new TRPCError({ code: "BAD_REQUEST", message: "invalid_code" });
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { createHash } from "crypto";
|
||||
import { totp } from "otplib";
|
||||
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { totpRawCheck } from "@calcom/lib/totp";
|
||||
import type { ZVerifyCodeInputSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
|
@ -31,8 +31,7 @@ export const verifyCodeHandler = async ({ ctx, input }: VerifyCodeOptions) => {
|
|||
.update(email + process.env.CALENDSO_ENCRYPTION_KEY)
|
||||
.digest("hex");
|
||||
|
||||
totp.options = { step: 900 };
|
||||
const isValidToken = totp.check(code, secret);
|
||||
const isValidToken = totpRawCheck(code, secret, { step: 900 });
|
||||
|
||||
if (!isValidToken) throw new TRPCError({ code: "BAD_REQUEST", message: "invalid_code" });
|
||||
|
||||
|
|
Loading…
Reference in New Issue