Removed all references to commonPasswords & the data file. This api route can be deprecated in favor of the client-side secure call to the haveIBeenPwnd password API. Further the datafile contains no passwords that meet the minimum password criteria.

This commit is contained in:
Joel Biddle
2023-08-22 23:30:24 +10:00
parent 1f60a3d73e
commit 3e36adcf5c
10 changed files with 208 additions and 1793 deletions
+82 -93
View File
@@ -1,32 +1,20 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import fs from "fs";
import path from "path";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import * as bigintConversion from "bigint-conversion"; import * as bigintConversion from "bigint-conversion";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const jsrp = require("jsrp"); const jsrp = require("jsrp");
import { import { LoginSRPDetail, TokenVersion, User } from "../../models";
LoginSRPDetail,
TokenVersion,
User,
} from "../../models";
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth"; import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user"; import { checkUserDevice } from "../../helpers/user";
import { import { ACTION_LOGIN, ACTION_LOGOUT } from "../../variables";
ACTION_LOGIN, import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
ACTION_LOGOUT,
} from "../../variables";
import {
BadRequestError,
UnauthorizedRequestError,
} from "../../utils/errors";
import { EELogService } from "../../ee/services"; import { EELogService } from "../../ee/services";
import { getUserAgentType } from "../../utils/posthog"; import { getUserAgentType } from "../../utils/posthog";
import { import {
getHttpsEnabled, getHttpsEnabled,
getJwtAuthLifetime, getJwtAuthLifetime,
getJwtAuthSecret, getJwtAuthSecret,
getJwtRefreshSecret, getJwtRefreshSecret
} from "../../config"; } from "../../config";
import { ActorType } from "../../ee/models"; import { ActorType } from "../../ee/models";
@@ -44,13 +32,10 @@ declare module "jsonwebtoken" {
* @returns * @returns
*/ */
export const login1 = async (req: Request, res: Response) => { export const login1 = async (req: Request, res: Response) => {
const { const { email, clientPublicKey }: { email: string; clientPublicKey: string } = req.body;
email,
clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email, email
}).select("+salt +verifier"); }).select("+salt +verifier");
if (!user) throw new Error("Failed to find user"); if (!user) throw new Error("Failed to find user");
@@ -59,21 +44,25 @@ export const login1 = async (req: Request, res: Response) => {
server.init( server.init(
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier
}, },
async () => { async () => {
// generate server-side public key // generate server-side public key
const serverPublicKey = server.getPublicKey(); const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: email }, { await LoginSRPDetail.findOneAndReplace(
email: email, { email: email },
clientPublicKey: clientPublicKey, {
serverBInt: bigintConversion.bigintToBuf(server.bInt), email: email,
}, { upsert: true, returnNewDocument: false }) clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
},
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({ return res.status(200).send({
serverPublicKey, serverPublicKey,
salt: user.salt, salt: user.salt
}); });
} }
); );
@@ -89,15 +78,19 @@ export const login1 = async (req: Request, res: Response) => {
export const login2 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => {
const { email, clientProof } = req.body; const { email, clientProof } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email, email
}).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag"); }).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag");
if (!user) throw new Error("Failed to find user"); if (!user) throw new Error("Failed to find user");
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email }) const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email });
if (!loginSRPDetailFromDB) { if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again")) return BadRequestError(
Error(
"It looks like some details from the first login are not found. Please try login one again"
)
);
} }
const server = new jsrp.server(); const server = new jsrp.server();
@@ -105,7 +98,7 @@ export const login2 = async (req: Request, res: Response) => {
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt, b: loginSRPDetailFromDB.serverBInt
}, },
async () => { async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey); server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
@@ -117,13 +110,13 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? ""
}); });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
@@ -131,20 +124,21 @@ export const login2 = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: "/", path: "/",
sameSite: "strict", sameSite: "strict",
secure: await getHttpsEnabled(), secure: await getHttpsEnabled()
}); });
const loginAction = await EELogService.createAction({ const loginAction = await EELogService.createAction({
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id, userId: user._id
}); });
loginAction && await EELogService.createLog({ loginAction &&
userId: user._id, (await EELogService.createLog({
actions: [loginAction], userId: user._id,
channel: getUserAgentType(req.headers["user-agent"]), actions: [loginAction],
ipAddress: req.realIP, channel: getUserAgentType(req.headers["user-agent"]),
}); ipAddress: req.realIP
}));
// return (access) token in response // return (access) token in response
return res.status(200).send({ return res.status(200).send({
@@ -152,12 +146,12 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey, publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey, encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv, iv: user.iv,
tag: user.tag, tag: user.tag
}); });
} }
return res.status(400).send({ return res.status(400).send({
message: "Failed to authenticate. Try again?", message: "Failed to authenticate. Try again?"
}); });
} }
); );
@@ -171,7 +165,7 @@ export const login2 = async (req: Request, res: Response) => {
*/ */
export const logout = async (req: Request, res: Response) => { export const logout = async (req: Request, res: Response) => {
if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) { if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) {
await clearTokens(req.authData.tokenVersionId) await clearTokens(req.authData.tokenVersionId);
} }
// clear httpOnly cookie // clear httpOnly cookie
@@ -179,49 +173,44 @@ export const logout = async (req: Request, res: Response) => {
httpOnly: true, httpOnly: true,
path: "/", path: "/",
sameSite: "strict", sameSite: "strict",
secure: (await getHttpsEnabled()) as boolean, secure: (await getHttpsEnabled()) as boolean
}); });
const logoutAction = await EELogService.createAction({ const logoutAction = await EELogService.createAction({
name: ACTION_LOGOUT, name: ACTION_LOGOUT,
userId: req.user._id, userId: req.user._id
}); });
logoutAction && await EELogService.createLog({ logoutAction &&
userId: req.user._id, (await EELogService.createLog({
actions: [logoutAction], userId: req.user._id,
channel: getUserAgentType(req.headers["user-agent"]), actions: [logoutAction],
ipAddress: req.realIP, channel: getUserAgentType(req.headers["user-agent"]),
}); ipAddress: req.realIP
}));
return res.status(200).send({ return res.status(200).send({
message: "Successfully logged out.", message: "Successfully logged out."
}); });
}; };
export const getCommonPasswords = async (req: Request, res: Response) => {
const commonPasswords = fs.readFileSync(
path.resolve(__dirname, "../../data/" + "common_passwords.txt"),
"utf8"
).split("\n");
return res.status(200).send(commonPasswords);
}
export const revokeAllSessions = async (req: Request, res: Response) => { export const revokeAllSessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany({ await TokenVersion.updateMany(
user: req.user._id, {
}, { user: req.user._id
$inc: {
refreshVersion: 1,
accessVersion: 1,
}, },
}); {
$inc: {
refreshVersion: 1,
accessVersion: 1
}
}
);
return res.status(200).send({ return res.status(200).send({
message: "Successfully revoked all sessions.", message: "Successfully revoked all sessions."
}); });
} };
/** /**
* Return user is authenticated * Return user is authenticated
@@ -231,9 +220,9 @@ export const revokeAllSessions = async (req: Request, res: Response) => {
*/ */
export const checkAuth = async (req: Request, res: Response) => { export const checkAuth = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
message: "Authenticated", message: "Authenticated"
}); });
} };
/** /**
* Return new JWT access token by first validating the refresh token * Return new JWT access token by first validating the refresh token
@@ -244,47 +233,47 @@ export const checkAuth = async (req: Request, res: Response) => {
export const getNewToken = async (req: Request, res: Response) => { export const getNewToken = async (req: Request, res: Response) => {
const refreshToken = req.cookies.jid; const refreshToken = req.cookies.jid;
if (!refreshToken) throw BadRequestError({ if (!refreshToken)
message: "Failed to find refresh token in request cookies" throw BadRequestError({
}); message: "Failed to find refresh token in request cookies"
});
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>jwt.verify(refreshToken, await getJwtRefreshSecret());
jwt.verify(refreshToken, await getJwtRefreshSecret())
);
const user = await User.findOne({ const user = await User.findOne({
_id: decodedToken.userId, _id: decodedToken.userId
}).select("+publicKey +refreshVersion +accessVersion"); }).select("+publicKey +refreshVersion +accessVersion");
if (!user) throw new Error("Failed to authenticate unfound user"); if (!user) throw new Error("Failed to authenticate unfound user");
if (!user?.publicKey) if (!user?.publicKey) throw new Error("Failed to authenticate not fully set up account");
throw new Error("Failed to authenticate not fully set up account");
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId); const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
if (!tokenVersion) throw UnauthorizedRequestError({ if (!tokenVersion)
message: "Failed to validate refresh token", throw UnauthorizedRequestError({
}); message: "Failed to validate refresh token"
});
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({ if (decodedToken.refreshVersion !== tokenVersion.refreshVersion)
message: "Failed to validate refresh token", throw BadRequestError({
}); message: "Failed to validate refresh token"
});
const token = createToken({ const token = createToken({
payload: { payload: {
userId: decodedToken.userId, userId: decodedToken.userId,
tokenVersionId: tokenVersion._id.toString(), tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.refreshVersion, accessVersion: tokenVersion.refreshVersion
}, },
expiresIn: await getJwtAuthLifetime(), expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret(), secret: await getJwtAuthSecret()
}); });
return res.status(200).send({ return res.status(200).send({
token, token
}); });
}; };
export const handleAuthProviderCallback = (req: Request, res: Response) => { export const handleAuthProviderCallback = (req: Request, res: Response) => {
res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`); res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`);
} };
File diff suppressed because it is too large Load Diff
+9 -12
View File
@@ -8,7 +8,8 @@ import { AuthMode } from "../../variables";
router.post("/token", validateRequest, authController.getNewToken); router.post("/token", validateRequest, authController.getNewToken);
router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login1) router.post(
// TODO endpoint: deprecate (moved to api/v3/auth/login1)
"/login1", "/login1",
authLimiter, authLimiter,
body("email").exists().trim().notEmpty(), body("email").exists().trim().notEmpty(),
@@ -17,7 +18,8 @@ router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login1)
authController.login1 authController.login1
); );
router.post( // TODO endpoint: deprecate (moved to api/v3/auth/login2) router.post(
// TODO endpoint: deprecate (moved to api/v3/auth/login2)
"/login2", "/login2",
authLimiter, authLimiter,
body("email").exists().trim().notEmpty(), body("email").exists().trim().notEmpty(),
@@ -30,7 +32,7 @@ router.post(
"/logout", "/logout",
authLimiter, authLimiter,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
authController.logout authController.logout
); );
@@ -38,22 +40,17 @@ router.post(
router.post( router.post(
"/checkAuth", "/checkAuth",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
authController.checkAuth authController.checkAuth
); );
router.get( router.delete(
"/common-passwords", // TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
authLimiter,
authController.getCommonPasswords
);
router.delete( // TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
"/sessions", "/sessions",
authLimiter, authLimiter,
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT], acceptedAuthModes: [AuthMode.JWT]
}), }),
authController.revokeAllSessions authController.revokeAllSessions
); );
@@ -8,7 +8,6 @@ import jsrp from "jsrp";
import nacl from "tweetnacl"; import nacl from "tweetnacl";
import { encodeBase64 } from "tweetnacl-util"; import { encodeBase64 } from "tweetnacl-util";
import { useGetCommonPasswords } from "@app/hooks/api";
import { completeAccountSignup } from "@app/hooks/api/auth/queries"; import { completeAccountSignup } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import ProjectService from "@app/services/ProjectService"; import ProjectService from "@app/services/ProjectService";
@@ -47,7 +46,6 @@ type Errors = {
specialChar?: string; specialChar?: string;
repeatedChar?: string; repeatedChar?: string;
isBeachedPassword?: string; isBeachedPassword?: string;
isCommonPassword?: string;
}; };
/** /**
@@ -76,7 +74,6 @@ export default function UserInfoStep({
setAttributionSource, setAttributionSource,
providerAuthToken providerAuthToken
}: UserInfoStepProps): JSX.Element { }: UserInfoStepProps): JSX.Element {
const { data: commonPasswords } = useGetCommonPasswords();
const [nameError, setNameError] = useState(false); const [nameError, setNameError] = useState(false);
const [organizationNameError, setOrganizationNameError] = useState(false); const [organizationNameError, setOrganizationNameError] = useState(false);
@@ -105,7 +102,6 @@ export default function UserInfoStep({
errorCheck = await checkPassword({ errorCheck = await checkPassword({
password, password,
commonPasswords,
setErrors setErrors
}); });
@@ -272,11 +268,10 @@ export default function UserInfoStep({
<div className="mt-2 flex max-h-60 w-1/4 w-full min-w-[20rem] flex-col items-center justify-center rounded-lg py-2 lg:w-1/6"> <div className="mt-2 flex max-h-60 w-1/4 w-full min-w-[20rem] flex-col items-center justify-center rounded-lg py-2 lg:w-1/6">
<InputField <InputField
label={t("section.password.password")} label={t("section.password.password")}
onChangeHandler={(pass: string) => { onChangeHandler={async (pass: string) => {
setPassword(pass); setPassword(pass);
checkPassword({ await checkPassword({
password: pass, password: pass,
commonPasswords,
setErrors setErrors
}); });
}} }}
@@ -9,12 +9,10 @@ type Errors = {
specialChar?: string; specialChar?: string;
repeatedChar?: string; repeatedChar?: string;
isBreachedPassword?: string; isBreachedPassword?: string;
isCommonPassword?: string;
}; };
interface CheckPasswordParams { interface CheckPasswordParams {
password: string; password: string;
commonPasswords: string[];
setErrors: (value: Errors) => void; setErrors: (value: Errors) => void;
} }
@@ -28,7 +26,6 @@ interface CheckPasswordParams {
* - Contains at least 1 special character * - Contains at least 1 special character
* - Does not contain 3 repeat, consecutive characters * - Does not contain 3 repeat, consecutive characters
* - Is not in a database of breached passwords * - Is not in a database of breached passwords
* - Is not in a list of common passwords
* *
* The function returns whether or not the password [password] * The function returns whether or not the password [password]
* passes the minimum requirements above. It sets errors on * passes the minimum requirements above. It sets errors on
@@ -38,11 +35,7 @@ interface CheckPasswordParams {
* @param {String} obj.password - the password to check * @param {String} obj.password - the password to check
* @param {Function} obj.setErrors - set state function to set error object * @param {Function} obj.setErrors - set state function to set error object
*/ */
const checkPassword = async ({ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Promise<boolean> => {
password,
commonPasswords,
setErrors
}: CheckPasswordParams): Promise<boolean> => {
const errors: Errors = {}; const errors: Errors = {};
// tooShort // tooShort
@@ -86,11 +79,6 @@ const checkPassword = async ({
"The password you provided is in a list of passwords commonly used on other websites. Please try again with a stronger password."; "The password you provided is in a list of passwords commonly used on other websites. Please try again with a stronger password.";
} }
// commonPassword
if (commonPasswords.includes(password)) {
errors.isCommonPassword = "No common passwords";
}
setErrors(errors); setErrors(errors);
return Object.keys(errors).length > 0; return Object.keys(errors).length > 0;
}; };
+2 -2
View File
@@ -1,10 +1,10 @@
export { export {
useGetAuthToken, useGetAuthToken,
useGetCommonPasswords,
useResetPassword, useResetPassword,
useSendMfaToken, useSendMfaToken,
useSendPasswordResetEmail, useSendPasswordResetEmail,
useSendVerificationEmail, useSendVerificationEmail,
useVerifyEmailVerificationCode, useVerifyEmailVerificationCode,
useVerifyMfaToken, useVerifyMfaToken,
useVerifyPasswordResetCode} from "./queries" useVerifyPasswordResetCode
} from "./queries";
+46 -76
View File
@@ -20,22 +20,22 @@ import {
SRPR1Res, SRPR1Res,
VerifyMfaTokenDTO, VerifyMfaTokenDTO,
VerifyMfaTokenRes, VerifyMfaTokenRes,
VerifySignupInviteDTO} from "./types"; VerifySignupInviteDTO
} from "./types";
const authKeys = { const authKeys = {
getAuthToken: ["token"] as const, getAuthToken: ["token"] as const
commonPasswords: ["common-passwords"] as const
}; };
export const login1 = async (loginDetails: Login1DTO) => { export const login1 = async (loginDetails: Login1DTO) => {
const { data } = await apiRequest.post<Login1Res>("/api/v3/auth/login1", loginDetails); const { data } = await apiRequest.post<Login1Res>("/api/v3/auth/login1", loginDetails);
return data; return data;
} };
export const login2 = async (loginDetails: Login2DTO) => { export const login2 = async (loginDetails: Login2DTO) => {
const { data } = await apiRequest.post<Login2Res>("/api/v3/auth/login2", loginDetails); const { data } = await apiRequest.post<Login2Res>("/api/v3/auth/login2", loginDetails);
return data; return data;
} };
export const useLogin1 = () => { export const useLogin1 = () => {
return useMutation({ return useMutation({
@@ -47,7 +47,7 @@ export const useLogin1 = () => {
return login1(details); return login1(details);
} }
}); });
} };
export const useLogin2 = () => { export const useLogin2 = () => {
return useMutation({ return useMutation({
@@ -59,22 +59,22 @@ export const useLogin2 = () => {
return login2(details); return login2(details);
} }
}); });
} };
export const srp1 = async (details: SRP1DTO) => { export const srp1 = async (details: SRP1DTO) => {
const { data } = await apiRequest.post<SRPR1Res>("/api/v1/password/srp1", details); const { data } = await apiRequest.post<SRPR1Res>("/api/v1/password/srp1", details);
return data; return data;
} };
export const completeAccountSignup = async (details: CompleteAccountSignupDTO) => { export const completeAccountSignup = async (details: CompleteAccountSignupDTO) => {
const { data } = await apiRequest.post("/api/v3/signup/complete-account/signup", details); const { data } = await apiRequest.post("/api/v3/signup/complete-account/signup", details);
return data; return data;
} };
export const completeAccountSignupInvite = async (details: CompleteAccountDTO) => { export const completeAccountSignupInvite = async (details: CompleteAccountDTO) => {
const { data } = await apiRequest.post("/api/v2/signup/complete-account/invite", details); const { data } = await apiRequest.post("/api/v2/signup/complete-account/invite", details);
return data; return data;
} };
export const useCompleteAccountSignup = () => { export const useCompleteAccountSignup = () => {
return useMutation({ return useMutation({
@@ -82,7 +82,7 @@ export const useCompleteAccountSignup = () => {
return completeAccountSignup(details); return completeAccountSignup(details);
} }
}); });
} };
export const useSendMfaToken = () => { export const useSendMfaToken = () => {
return useMutation<{}, {}, SendMfaTokenDTO>({ return useMutation<{}, {}, SendMfaTokenDTO>({
@@ -91,22 +91,16 @@ export const useSendMfaToken = () => {
return data; return data;
} }
}); });
} };
export const verifyMfaToken = async ({ export const verifyMfaToken = async ({ email, mfaCode }: { email: string; mfaCode: string }) => {
email,
mfaCode
}: {
email: string;
mfaCode: string;
}) => {
const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", { const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", {
email, email,
mfaToken: mfaCode mfaToken: mfaCode
}); });
return data; return data;
} };
export const useVerifyMfaToken = () => { export const useVerifyMfaToken = () => {
return useMutation<VerifyMfaTokenRes, {}, VerifyMfaTokenDTO>({ return useMutation<VerifyMfaTokenRes, {}, VerifyMfaTokenDTO>({
@@ -117,20 +111,16 @@ export const useVerifyMfaToken = () => {
}); });
} }
}); });
} };
export const verifySignupInvite = async (details: VerifySignupInviteDTO) => { export const verifySignupInvite = async (details: VerifySignupInviteDTO) => {
const { data } = await apiRequest.post("/api/v1/invite-org/verify", details); const { data } = await apiRequest.post("/api/v1/invite-org/verify", details);
return data; return data;
} };
export const useSendVerificationEmail = () => { export const useSendVerificationEmail = () => {
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({ email }: { email: string }) => {
email
}: {
email: string;
}) => {
const { data } = await apiRequest.post("/api/v1/signup/email/signup", { const { data } = await apiRequest.post("/api/v1/signup/email/signup", {
email email
}); });
@@ -138,17 +128,11 @@ export const useSendVerificationEmail = () => {
return data; return data;
} }
}); });
} };
export const useVerifyEmailVerificationCode = () => { export const useVerifyEmailVerificationCode = () => {
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({ email, code }: { email: string; code: string }) => {
email,
code
}: {
email: string;
code: string;
}) => {
const { data } = await apiRequest.post("/api/v1/signup/email/verify", { const { data } = await apiRequest.post("/api/v1/signup/email/verify", {
email, email,
code code
@@ -157,15 +141,11 @@ export const useVerifyEmailVerificationCode = () => {
return data; return data;
} }
}); });
} };
export const useSendPasswordResetEmail = () => { export const useSendPasswordResetEmail = () => {
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({ email }: { email: string }) => {
email
}: {
email: string;
}) => {
const { data } = await apiRequest.post("/api/v1/password/email/password-reset", { const { data } = await apiRequest.post("/api/v1/password/email/password-reset", {
email email
}); });
@@ -173,17 +153,11 @@ export const useSendPasswordResetEmail = () => {
return data; return data;
} }
}); });
} };
export const useVerifyPasswordResetCode = () => { export const useVerifyPasswordResetCode = () => {
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({ email, code }: { email: string; code: string }) => {
email,
code
}: {
email: string;
code: string;
}) => {
const { data } = await apiRequest.post("/api/v1/password/email/password-reset-verify", { const { data } = await apiRequest.post("/api/v1/password/email/password-reset-verify", {
email, email,
code code
@@ -192,12 +166,12 @@ export const useVerifyPasswordResetCode = () => {
return data; return data;
} }
}); });
} };
export const issueBackupPrivateKey = async (details: IssueBackupPrivateKeyDTO) => { export const issueBackupPrivateKey = async (details: IssueBackupPrivateKeyDTO) => {
const { data } = await apiRequest.post("/api/v1/password/backup-private-key", details); const { data } = await apiRequest.post("/api/v1/password/backup-private-key", details);
return data; return data;
} };
export const getBackupEncryptedPrivateKey = async ({ export const getBackupEncryptedPrivateKey = async ({
verificationToken verificationToken
@@ -209,35 +183,39 @@ export const getBackupEncryptedPrivateKey = async ({
}); });
return data.backupPrivateKey; return data.backupPrivateKey;
} };
export const useResetPassword = () => { export const useResetPassword = () => {
return useMutation({ return useMutation({
mutationFn: async (details: ResetPasswordDTO) => { mutationFn: async (details: ResetPasswordDTO) => {
const { data } = await apiRequest.post("/api/v1/password/password-reset", { const { data } = await apiRequest.post(
protectedKey: details.protectedKey, "/api/v1/password/password-reset",
protectedKeyIV: details.protectedKeyIV, {
protectedKeyTag: details.protectedKeyTag, protectedKey: details.protectedKey,
encryptedPrivateKey: details.encryptedPrivateKey, protectedKeyIV: details.protectedKeyIV,
encryptedPrivateKeyIV: details.encryptedPrivateKeyIV, protectedKeyTag: details.protectedKeyTag,
encryptedPrivateKeyTag: details.encryptedPrivateKeyTag, encryptedPrivateKey: details.encryptedPrivateKey,
salt: details.salt, encryptedPrivateKeyIV: details.encryptedPrivateKeyIV,
verifier: details.verifier encryptedPrivateKeyTag: details.encryptedPrivateKeyTag,
}, { salt: details.salt,
headers: { verifier: details.verifier
Authorization: `Bearer ${details.verificationToken}` },
{
headers: {
Authorization: `Bearer ${details.verificationToken}`
}
} }
}); );
return data; return data;
} }
}); });
} };
export const changePassword = async (details: ChangePasswordDTO) => { export const changePassword = async (details: ChangePasswordDTO) => {
const { data } = await apiRequest.post("/api/v1/password/change-password", details); const { data } = await apiRequest.post("/api/v1/password/change-password", details);
return data; return data;
} };
export const useChangePassword = () => { export const useChangePassword = () => {
// note: use after srp1 // note: use after srp1
@@ -246,7 +224,7 @@ export const useChangePassword = () => {
return changePassword(details); return changePassword(details);
} }
}); });
} };
// Refresh token is set as cookie when logged in // Refresh token is set as cookie when logged in
// Using that we fetch the auth bearer token needed for auth calls // Using that we fetch the auth bearer token needed for auth calls
@@ -263,11 +241,3 @@ export const useGetAuthToken = () =>
onSuccess: (data) => setAuthToken(data.token), onSuccess: (data) => setAuthToken(data.token),
retry: 0 retry: 0
}); });
const fetchCommonPasswords = async () => {
const { data } = await apiRequest.get("/api/v1/auth/common-passwords");
return data || [];
};
export const useGetCommonPasswords = () =>
useQuery({ queryKey: authKeys.commonPasswords, queryFn: fetchCommonPasswords });
+45 -62
View File
@@ -22,31 +22,23 @@ import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey"; import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey";
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { import { completeAccountSignupInvite, verifySignupInvite } from "@app/hooks/api/auth/queries";
useGetCommonPasswords
} from "@app/hooks/api";
import {
completeAccountSignupInvite,
verifySignupInvite
} from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries";
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
const client = new jsrp.client(); const client = new jsrp.client();
type Errors = { type Errors = {
length?: string, length?: string;
upperCase?: string, upperCase?: string;
lowerCase?: string, lowerCase?: string;
number?: string, number?: string;
specialChar?: string, specialChar?: string;
repeatedChar?: string, repeatedChar?: string;
breachedPassword?: string breachedPassword?: string;
}; };
export default function SignupInvite() { export default function SignupInvite() {
const { data: commonPasswords } = useGetCommonPasswords();
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [firstName, setFirstName] = useState(""); const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState(""); const [lastName, setLastName] = useState("");
@@ -83,7 +75,6 @@ export default function SignupInvite() {
errorCheck = await checkPassword({ errorCheck = await checkPassword({
password, password,
commonPasswords,
setErrors setErrors
}); });
@@ -140,9 +131,7 @@ export default function SignupInvite() {
secret: Buffer.from(derivedKey.hash) secret: Buffer.from(derivedKey.hash)
}); });
const { const { token: jwtToken } = await completeAccountSignupInvite({
token: jwtToken
} = await completeAccountSignupInvite({
email, email,
firstName, firstName,
lastName, lastName,
@@ -162,11 +151,11 @@ export default function SignupInvite() {
SecurityClient.setToken(jwtToken); SecurityClient.setToken(jwtToken);
saveTokenToLocalStorage({ saveTokenToLocalStorage({
publicKey, publicKey,
encryptedPrivateKey, encryptedPrivateKey,
iv: encryptedPrivateKeyIV, iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag, tag: encryptedPrivateKeyTag,
privateKey privateKey
}); });
const userOrgs = await fetchOrganizations(); const userOrgs = await fetchOrganizations();
@@ -189,12 +178,12 @@ export default function SignupInvite() {
// Step 4 of the sign up process (download the emergency kit pdf) // Step 4 of the sign up process (download the emergency kit pdf)
const stepConfirmEmail = ( const stepConfirmEmail = (
<div className="border border-mineshaft-600 bg-mineshaft-800 flex flex-col items-center w-full max-w-xs md:max-w-lg h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl"> <div className="h-7/12 mx-1 mb-36 flex w-full max-w-xs flex-col items-center rounded-xl border border-mineshaft-600 bg-mineshaft-800 py-8 px-4 drop-shadow-xl md:mb-16 md:max-w-lg md:px-6">
<p className="text-4xl text-center font-semibold mb-6 flex justify-center text-primary-100"> <p className="mb-6 flex justify-center text-center text-4xl font-semibold text-primary-100">
Confirm your email Confirm your email
</p> </p>
<Image src="/images/dragon-signupinvite.svg" height={262} width={410} alt="verify email" /> <Image src="/images/dragon-signupinvite.svg" height={262} width={410} alt="verify email" />
<div className="flex flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-10 mb-2"> <div className="mx-auto mt-10 mb-2 flex max-h-24 max-w-md flex-col items-center justify-center px-4 text-lg md:p-2">
<Button <Button
text="Confirm Email" text="Confirm Email"
onButtonPressed={async () => { onButtonPressed={async () => {
@@ -230,11 +219,11 @@ export default function SignupInvite() {
// Because this is the invite signup - we directly go to the last step of signup (email is already verified) // Because this is the invite signup - we directly go to the last step of signup (email is already verified)
const main = ( const main = (
<div className="border border-mineshaft-600 bg-mineshaft-800 w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 md:mb-16"> <div className="h-7/12 mx-auto mb-32 w-max rounded-xl border border-mineshaft-600 bg-mineshaft-800 py-10 px-8 drop-shadow-xl md:mb-16">
<p className="text-4xl font-bold flex justify-center mb-6 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-tr from-mineshaft-300 to-white"> <p className="mx-8 mb-6 flex justify-center bg-gradient-to-tr from-mineshaft-300 to-white bg-clip-text text-4xl font-bold text-transparent md:mx-16">
Almost there! Almost there!
</p> </p>
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24"> <div className="relative z-0 flex max-h-24 w-full items-center justify-end rounded-lg md:p-2">
<InputField <InputField
label="First Name" label="First Name"
onChangeHandler={setFirstName} onChangeHandler={setFirstName}
@@ -246,7 +235,7 @@ export default function SignupInvite() {
autoComplete="given-name" autoComplete="given-name"
/> />
</div> </div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24"> <div className="flex max-h-24 w-full items-center justify-center rounded-lg md:p-2">
<InputField <InputField
label="Last Name" label="Last Name"
onChangeHandler={setLastName} onChangeHandler={setLastName}
@@ -258,14 +247,13 @@ export default function SignupInvite() {
autoComplete="family-name" autoComplete="family-name"
/> />
</div> </div>
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60"> <div className="mt-2 flex max-h-60 w-full flex-col items-center justify-center rounded-lg md:p-2">
<InputField <InputField
label="Password" label="Password"
onChangeHandler={(pass) => { onChangeHandler={(pass) => {
setPassword(pass); setPassword(pass);
checkPassword({ checkPassword({
password: pass, password: pass,
commonPasswords,
setErrors setErrors
}); });
}} }}
@@ -277,31 +265,26 @@ export default function SignupInvite() {
id="new-password" id="new-password"
/> />
{Object.keys(errors).length > 0 && ( {Object.keys(errors).length > 0 && (
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2"> <div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
<div className="mb-2 text-sm text-gray-400">Password should contain at least:</div> <div className="mb-2 text-sm text-gray-400">Password should contain at least:</div>
{Object.keys(errors).map((key) => { {Object.keys(errors).map((key) => {
if (errors[key as keyof Errors]) { if (errors[key as keyof Errors]) {
return ( return (
<div className="ml-1 flex flex-row items-top justify-start" key={key}> <div className="items-top ml-1 flex flex-row justify-start" key={key}>
<div> <div>
<FontAwesomeIcon <FontAwesomeIcon icon={faXmark} className="text-md ml-0.5 mr-2.5 text-red" />
icon={faXmark}
className="text-md text-red ml-0.5 mr-2.5"
/>
</div>
<p className="text-gray-400 text-sm">
{errors[key as keyof Errors]}
</p>
</div> </div>
); <p className="text-sm text-gray-400">{errors[key as keyof Errors]}</p>
} </div>
);
}
return null; return null;
})} })}
</div> </div>
)} )}
</div> </div>
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg"> <div className="mx-auto mt-2 flex max-h-24 max-w-max flex-col items-center justify-center px-2 py-3 text-lg md:px-4 md:py-5">
<Button <Button
text="Sign Up" text="Sign Up"
onButtonPressed={() => { onButtonPressed={() => {
@@ -316,21 +299,21 @@ export default function SignupInvite() {
// Step 4 of the sign up process (download the emergency kit pdf) // Step 4 of the sign up process (download the emergency kit pdf)
const step4 = ( const step4 = (
<div className="border border-mineshaft-600 bg-mineshaft-800 flex flex-col items-center w-full max-w-xs md:max-w-lg h-7/12 pt-8 pb-6 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl"> <div className="h-7/12 mx-1 mb-36 flex w-full max-w-xs flex-col items-center rounded-xl border border-mineshaft-600 bg-mineshaft-800 px-4 pt-8 pb-6 drop-shadow-xl md:mb-16 md:max-w-lg md:px-6">
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-white to-mineshaft-300"> <p className="flex justify-center bg-gradient-to-br from-white to-mineshaft-300 bg-clip-text text-center text-4xl font-semibold text-transparent">
Save your Emergency Kit Save your Emergency Kit
</p> </p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2"> <div className="text-md mt-4 flex w-full max-w-md flex-col items-center justify-center rounded-md px-2 text-gray-400 md:mt-8">
<div> <div>
If you get locked out of your account, your Emergency Kit is the only way to sign in. If you get locked out of your account, your Emergency Kit is the only way to sign in.
</div> </div>
<div className="mt-3">We recommend you download it and keep it somewhere safe.</div> <div className="mt-3">We recommend you download it and keep it somewhere safe.</div>
</div> </div>
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4"> <div className="mx-auto mt-4 flex w-full max-w-xs flex-row items-center rounded-md bg-white/10 p-2 text-gray-400 md:max-w-md">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" /> <FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
It contains your Secret Key which we cannot access or recover for you if you lose it. It contains your Secret Key which we cannot access or recover for you if you lose it.
</div> </div>
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-4 px-2 py-3 max-h-24 max-w-max mx-auto text-lg"> <div className="mx-auto mt-4 flex max-h-24 max-w-max flex-col items-center justify-center px-2 py-3 text-lg md:px-4 md:py-5">
<Button <Button
text="Download PDF" text="Download PDF"
onButtonPressed={async () => { onButtonPressed={async () => {
@@ -350,7 +333,7 @@ export default function SignupInvite() {
); );
return ( return (
<div className="bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 h-screen flex flex-col items-center justify-center"> <div className="flex h-screen flex-col items-center justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700">
<Head> <Head>
<title>Sign Up</title> <title>Sign Up</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
@@ -11,7 +11,6 @@ import attemptChangePassword from "@app/components/utilities/attemptChangePasswo
import checkPassword from "@app/components/utilities/checks/checkPassword"; import checkPassword from "@app/components/utilities/checks/checkPassword";
import { Button, FormControl, Input } from "@app/components/v2"; import { Button, FormControl, Input } from "@app/components/v2";
import { useUser } from "@app/context"; import { useUser } from "@app/context";
import { useGetCommonPasswords } from "@app/hooks/api";
type Errors = { type Errors = {
tooShort?: string; tooShort?: string;
@@ -22,7 +21,6 @@ type Errors = {
specialChar?: string; specialChar?: string;
repeatedChar?: string; repeatedChar?: string;
isBreachedPassword?: string; isBreachedPassword?: string;
isCommonPassword?: string;
}; };
const schema = yup const schema = yup
@@ -38,7 +36,6 @@ export const ChangePasswordSection = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { user } = useUser(); const { user } = useUser();
const { data: commonPasswords } = useGetCommonPasswords();
const { reset, control, handleSubmit } = useForm({ const { reset, control, handleSubmit } = useForm({
defaultValues: { defaultValues: {
oldPassword: "", oldPassword: "",
@@ -52,11 +49,9 @@ export const ChangePasswordSection = () => {
const onFormSubmit = async ({ oldPassword, newPassword }: FormData) => { const onFormSubmit = async ({ oldPassword, newPassword }: FormData) => {
try { try {
if (!user?.email) return; if (!user?.email) return;
if (!commonPasswords) return;
const errorCheck = await checkPassword({ const errorCheck = await checkPassword({
password: newPassword, password: newPassword,
commonPasswords,
setErrors setErrors
}); });
@@ -15,7 +15,6 @@ import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { Button, Input } from "@app/components/v2"; import { Button, Input } from "@app/components/v2";
import { useGetCommonPasswords } from "@app/hooks/api";
import { completeAccountSignup } from "@app/hooks/api/auth/queries"; import { completeAccountSignup } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import ProjectService from "@app/services/ProjectService"; import ProjectService from "@app/services/ProjectService";
@@ -42,7 +41,6 @@ type Errors = {
specialChar?: string; specialChar?: string;
repeatedChar?: string; repeatedChar?: string;
isBeachedPassword?: string; isBeachedPassword?: string;
isCommonPassword?: string;
}; };
/** /**
@@ -67,7 +65,6 @@ export const UserInfoSSOStep = ({
setStep, setStep,
providerAuthToken providerAuthToken
}: Props) => { }: Props) => {
const { data: commonPasswords } = useGetCommonPasswords();
const [nameError, setNameError] = useState(false); const [nameError, setNameError] = useState(false);
const [organizationName, setOrganizationName] = useState(""); const [organizationName, setOrganizationName] = useState("");
const [organizationNameError, setOrganizationNameError] = useState(false); const [organizationNameError, setOrganizationNameError] = useState(false);
@@ -102,7 +99,6 @@ export const UserInfoSSOStep = ({
errorCheck = await checkPassword({ errorCheck = await checkPassword({
password, password,
commonPasswords,
setErrors setErrors
}); });
@@ -277,7 +273,6 @@ export const UserInfoSSOStep = ({
setPassword(pass); setPassword(pass);
await checkPassword({ await checkPassword({
password: pass, password: pass,
commonPasswords,
setErrors setErrors
}); });
}} }}