mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
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:
@@ -1,32 +1,20 @@
|
||||
import { Request, Response } from "express";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import jwt from "jsonwebtoken";
|
||||
import * as bigintConversion from "bigint-conversion";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const jsrp = require("jsrp");
|
||||
import {
|
||||
LoginSRPDetail,
|
||||
TokenVersion,
|
||||
User,
|
||||
} from "../../models";
|
||||
import { LoginSRPDetail, TokenVersion, User } from "../../models";
|
||||
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
|
||||
import { checkUserDevice } from "../../helpers/user";
|
||||
import {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
} from "../../variables";
|
||||
import {
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError,
|
||||
} from "../../utils/errors";
|
||||
import { ACTION_LOGIN, ACTION_LOGOUT } from "../../variables";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { EELogService } from "../../ee/services";
|
||||
import { getUserAgentType } from "../../utils/posthog";
|
||||
import {
|
||||
getHttpsEnabled,
|
||||
getJwtAuthLifetime,
|
||||
getJwtAuthSecret,
|
||||
getJwtRefreshSecret,
|
||||
getJwtRefreshSecret
|
||||
} from "../../config";
|
||||
import { ActorType } from "../../ee/models";
|
||||
|
||||
@@ -44,13 +32,10 @@ declare module "jsonwebtoken" {
|
||||
* @returns
|
||||
*/
|
||||
export const login1 = async (req: Request, res: Response) => {
|
||||
const {
|
||||
email,
|
||||
clientPublicKey,
|
||||
}: { email: string; clientPublicKey: string } = req.body;
|
||||
const { email, clientPublicKey }: { email: string; clientPublicKey: string } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
email,
|
||||
email
|
||||
}).select("+salt +verifier");
|
||||
|
||||
if (!user) throw new Error("Failed to find user");
|
||||
@@ -59,21 +44,25 @@ export const login1 = async (req: Request, res: Response) => {
|
||||
server.init(
|
||||
{
|
||||
salt: user.salt,
|
||||
verifier: user.verifier,
|
||||
verifier: user.verifier
|
||||
},
|
||||
async () => {
|
||||
// generate server-side public key
|
||||
const serverPublicKey = server.getPublicKey();
|
||||
|
||||
await LoginSRPDetail.findOneAndReplace({ email: email }, {
|
||||
email: email,
|
||||
clientPublicKey: clientPublicKey,
|
||||
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
||||
}, { upsert: true, returnNewDocument: false })
|
||||
await LoginSRPDetail.findOneAndReplace(
|
||||
{ email: email },
|
||||
{
|
||||
email: email,
|
||||
clientPublicKey: clientPublicKey,
|
||||
serverBInt: bigintConversion.bigintToBuf(server.bInt)
|
||||
},
|
||||
{ upsert: true, returnNewDocument: false }
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
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) => {
|
||||
const { email, clientProof } = req.body;
|
||||
const user = await User.findOne({
|
||||
email,
|
||||
email
|
||||
}).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag");
|
||||
|
||||
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) {
|
||||
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();
|
||||
@@ -105,7 +98,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
{
|
||||
salt: user.salt,
|
||||
verifier: user.verifier,
|
||||
b: loginSRPDetailFromDB.serverBInt,
|
||||
b: loginSRPDetailFromDB.serverBInt
|
||||
},
|
||||
async () => {
|
||||
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
|
||||
@@ -117,13 +110,13 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
await checkUserDevice({
|
||||
user,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
userAgent: req.headers["user-agent"] ?? ""
|
||||
});
|
||||
|
||||
const tokens = await issueAuthTokens({
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
userAgent: req.headers["user-agent"] ?? ""
|
||||
});
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
@@ -131,20 +124,21 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: await getHttpsEnabled(),
|
||||
secure: await getHttpsEnabled()
|
||||
});
|
||||
|
||||
const loginAction = await EELogService.createAction({
|
||||
name: ACTION_LOGIN,
|
||||
userId: user._id,
|
||||
userId: user._id
|
||||
});
|
||||
|
||||
loginAction && await EELogService.createLog({
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP,
|
||||
});
|
||||
loginAction &&
|
||||
(await EELogService.createLog({
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
// return (access) token in response
|
||||
return res.status(200).send({
|
||||
@@ -152,12 +146,12 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
publicKey: user.publicKey,
|
||||
encryptedPrivateKey: user.encryptedPrivateKey,
|
||||
iv: user.iv,
|
||||
tag: user.tag,
|
||||
tag: user.tag
|
||||
});
|
||||
}
|
||||
|
||||
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) => {
|
||||
if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) {
|
||||
await clearTokens(req.authData.tokenVersionId)
|
||||
await clearTokens(req.authData.tokenVersionId);
|
||||
}
|
||||
|
||||
// clear httpOnly cookie
|
||||
@@ -179,49 +173,44 @@ export const logout = async (req: Request, res: Response) => {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: (await getHttpsEnabled()) as boolean,
|
||||
secure: (await getHttpsEnabled()) as boolean
|
||||
});
|
||||
|
||||
const logoutAction = await EELogService.createAction({
|
||||
name: ACTION_LOGOUT,
|
||||
userId: req.user._id,
|
||||
userId: req.user._id
|
||||
});
|
||||
|
||||
logoutAction && await EELogService.createLog({
|
||||
userId: req.user._id,
|
||||
actions: [logoutAction],
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP,
|
||||
});
|
||||
logoutAction &&
|
||||
(await EELogService.createLog({
|
||||
userId: req.user._id,
|
||||
actions: [logoutAction],
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
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) => {
|
||||
await TokenVersion.updateMany({
|
||||
user: req.user._id,
|
||||
}, {
|
||||
$inc: {
|
||||
refreshVersion: 1,
|
||||
accessVersion: 1,
|
||||
await TokenVersion.updateMany(
|
||||
{
|
||||
user: req.user._id
|
||||
},
|
||||
});
|
||||
{
|
||||
$inc: {
|
||||
refreshVersion: 1,
|
||||
accessVersion: 1
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully revoked all sessions.",
|
||||
});
|
||||
}
|
||||
message: "Successfully revoked all sessions."
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return user is authenticated
|
||||
@@ -231,9 +220,9 @@ export const revokeAllSessions = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const checkAuth = async (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
message: "Authenticated",
|
||||
message: "Authenticated"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
const refreshToken = req.cookies.jid;
|
||||
|
||||
if (!refreshToken) throw BadRequestError({
|
||||
message: "Failed to find refresh token in request cookies"
|
||||
});
|
||||
if (!refreshToken)
|
||||
throw BadRequestError({
|
||||
message: "Failed to find refresh token in request cookies"
|
||||
});
|
||||
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(refreshToken, await getJwtRefreshSecret())
|
||||
);
|
||||
const decodedToken = <jwt.UserIDJwtPayload>jwt.verify(refreshToken, await getJwtRefreshSecret());
|
||||
|
||||
const user = await User.findOne({
|
||||
_id: decodedToken.userId,
|
||||
_id: decodedToken.userId
|
||||
}).select("+publicKey +refreshVersion +accessVersion");
|
||||
|
||||
if (!user) throw new Error("Failed to authenticate unfound user");
|
||||
if (!user?.publicKey)
|
||||
throw new Error("Failed to authenticate not fully set up account");
|
||||
|
||||
if (!user?.publicKey) throw new Error("Failed to authenticate not fully set up account");
|
||||
|
||||
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
|
||||
|
||||
if (!tokenVersion) throw UnauthorizedRequestError({
|
||||
message: "Failed to validate refresh token",
|
||||
});
|
||||
if (!tokenVersion)
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed to validate refresh token"
|
||||
});
|
||||
|
||||
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({
|
||||
message: "Failed to validate refresh token",
|
||||
});
|
||||
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion)
|
||||
throw BadRequestError({
|
||||
message: "Failed to validate refresh token"
|
||||
});
|
||||
|
||||
const token = createToken({
|
||||
payload: {
|
||||
userId: decodedToken.userId,
|
||||
tokenVersionId: tokenVersion._id.toString(),
|
||||
accessVersion: tokenVersion.refreshVersion,
|
||||
accessVersion: tokenVersion.refreshVersion
|
||||
},
|
||||
expiresIn: await getJwtAuthLifetime(),
|
||||
secret: await getJwtAuthSecret(),
|
||||
secret: await getJwtAuthSecret()
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
token,
|
||||
token
|
||||
});
|
||||
};
|
||||
|
||||
export const handleAuthProviderCallback = (req: Request, res: Response) => {
|
||||
res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,8 @@ import { AuthMode } from "../../variables";
|
||||
|
||||
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",
|
||||
authLimiter,
|
||||
body("email").exists().trim().notEmpty(),
|
||||
@@ -17,7 +18,8 @@ router.post( // TODO endpoint: deprecate (moved to api/v3/auth/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",
|
||||
authLimiter,
|
||||
body("email").exists().trim().notEmpty(),
|
||||
@@ -30,7 +32,7 @@ router.post(
|
||||
"/logout",
|
||||
authLimiter,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
authController.logout
|
||||
);
|
||||
@@ -38,24 +40,19 @@ router.post(
|
||||
router.post(
|
||||
"/checkAuth",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
authController.checkAuth
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/common-passwords",
|
||||
authLimiter,
|
||||
authController.getCommonPasswords
|
||||
);
|
||||
|
||||
router.delete( // TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
|
||||
router.delete(
|
||||
// TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
|
||||
"/sessions",
|
||||
authLimiter,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
authController.revokeAllSessions
|
||||
);
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
||||
@@ -8,7 +8,6 @@ import jsrp from "jsrp";
|
||||
import nacl from "tweetnacl";
|
||||
import { encodeBase64 } from "tweetnacl-util";
|
||||
|
||||
import { useGetCommonPasswords } from "@app/hooks/api";
|
||||
import { completeAccountSignup } from "@app/hooks/api/auth/queries";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import ProjectService from "@app/services/ProjectService";
|
||||
@@ -47,7 +46,6 @@ type Errors = {
|
||||
specialChar?: string;
|
||||
repeatedChar?: string;
|
||||
isBeachedPassword?: string;
|
||||
isCommonPassword?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -76,7 +74,6 @@ export default function UserInfoStep({
|
||||
setAttributionSource,
|
||||
providerAuthToken
|
||||
}: UserInfoStepProps): JSX.Element {
|
||||
const { data: commonPasswords } = useGetCommonPasswords();
|
||||
const [nameError, setNameError] = useState(false);
|
||||
const [organizationNameError, setOrganizationNameError] = useState(false);
|
||||
|
||||
@@ -105,7 +102,6 @@ export default function UserInfoStep({
|
||||
|
||||
errorCheck = await checkPassword({
|
||||
password,
|
||||
commonPasswords,
|
||||
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">
|
||||
<InputField
|
||||
label={t("section.password.password")}
|
||||
onChangeHandler={(pass: string) => {
|
||||
onChangeHandler={async (pass: string) => {
|
||||
setPassword(pass);
|
||||
checkPassword({
|
||||
await checkPassword({
|
||||
password: pass,
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -9,12 +9,10 @@ type Errors = {
|
||||
specialChar?: string;
|
||||
repeatedChar?: string;
|
||||
isBreachedPassword?: string;
|
||||
isCommonPassword?: string;
|
||||
};
|
||||
|
||||
interface CheckPasswordParams {
|
||||
password: string;
|
||||
commonPasswords: string[];
|
||||
setErrors: (value: Errors) => void;
|
||||
}
|
||||
|
||||
@@ -28,7 +26,6 @@ interface CheckPasswordParams {
|
||||
* - Contains at least 1 special character
|
||||
* - Does not contain 3 repeat, consecutive characters
|
||||
* - 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]
|
||||
* passes the minimum requirements above. It sets errors on
|
||||
@@ -38,11 +35,7 @@ interface CheckPasswordParams {
|
||||
* @param {String} obj.password - the password to check
|
||||
* @param {Function} obj.setErrors - set state function to set error object
|
||||
*/
|
||||
const checkPassword = async ({
|
||||
password,
|
||||
commonPasswords,
|
||||
setErrors
|
||||
}: CheckPasswordParams): Promise<boolean> => {
|
||||
const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Promise<boolean> => {
|
||||
const errors: Errors = {};
|
||||
|
||||
// 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.";
|
||||
}
|
||||
|
||||
// commonPassword
|
||||
if (commonPasswords.includes(password)) {
|
||||
errors.isCommonPassword = "No common passwords";
|
||||
}
|
||||
|
||||
setErrors(errors);
|
||||
return Object.keys(errors).length > 0;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export {
|
||||
useGetAuthToken,
|
||||
useGetCommonPasswords,
|
||||
useResetPassword,
|
||||
useSendMfaToken,
|
||||
useSendMfaToken,
|
||||
useSendPasswordResetEmail,
|
||||
useSendVerificationEmail,
|
||||
useVerifyEmailVerificationCode,
|
||||
useVerifyMfaToken,
|
||||
useVerifyPasswordResetCode} from "./queries"
|
||||
useVerifyPasswordResetCode
|
||||
} from "./queries";
|
||||
|
||||
@@ -20,22 +20,22 @@ import {
|
||||
SRPR1Res,
|
||||
VerifyMfaTokenDTO,
|
||||
VerifyMfaTokenRes,
|
||||
VerifySignupInviteDTO} from "./types";
|
||||
VerifySignupInviteDTO
|
||||
} from "./types";
|
||||
|
||||
const authKeys = {
|
||||
getAuthToken: ["token"] as const,
|
||||
commonPasswords: ["common-passwords"] as const
|
||||
getAuthToken: ["token"] as const
|
||||
};
|
||||
|
||||
export const login1 = async (loginDetails: Login1DTO) => {
|
||||
const { data } = await apiRequest.post<Login1Res>("/api/v3/auth/login1", loginDetails);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export const login2 = async (loginDetails: Login2DTO) => {
|
||||
const { data } = await apiRequest.post<Login2Res>("/api/v3/auth/login2", loginDetails);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export const useLogin1 = () => {
|
||||
return useMutation({
|
||||
@@ -47,7 +47,7 @@ export const useLogin1 = () => {
|
||||
return login1(details);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useLogin2 = () => {
|
||||
return useMutation({
|
||||
@@ -59,22 +59,22 @@ export const useLogin2 = () => {
|
||||
return login2(details);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const srp1 = async (details: SRP1DTO) => {
|
||||
const { data } = await apiRequest.post<SRPR1Res>("/api/v1/password/srp1", details);
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const completeAccountSignup = async (details: CompleteAccountSignupDTO) => {
|
||||
const { data } = await apiRequest.post("/api/v3/signup/complete-account/signup", details);
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const completeAccountSignupInvite = async (details: CompleteAccountDTO) => {
|
||||
const { data } = await apiRequest.post("/api/v2/signup/complete-account/invite", details);
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useCompleteAccountSignup = () => {
|
||||
return useMutation({
|
||||
@@ -82,7 +82,7 @@ export const useCompleteAccountSignup = () => {
|
||||
return completeAccountSignup(details);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useSendMfaToken = () => {
|
||||
return useMutation<{}, {}, SendMfaTokenDTO>({
|
||||
@@ -91,22 +91,16 @@ export const useSendMfaToken = () => {
|
||||
return data;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const verifyMfaToken = async ({
|
||||
email,
|
||||
mfaCode
|
||||
}: {
|
||||
email: string;
|
||||
mfaCode: string;
|
||||
}) => {
|
||||
export const verifyMfaToken = async ({ email, mfaCode }: { email: string; mfaCode: string }) => {
|
||||
const { data } = await apiRequest.post("/api/v2/auth/mfa/verify", {
|
||||
email,
|
||||
mfaToken: mfaCode
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export const useVerifyMfaToken = () => {
|
||||
return useMutation<VerifyMfaTokenRes, {}, VerifyMfaTokenDTO>({
|
||||
@@ -117,87 +111,67 @@ export const useVerifyMfaToken = () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const verifySignupInvite = async (details: VerifySignupInviteDTO) => {
|
||||
const { data } = await apiRequest.post("/api/v1/invite-org/verify", details);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export const useSendVerificationEmail = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
email
|
||||
}: {
|
||||
email: string;
|
||||
}) => {
|
||||
mutationFn: async ({ email }: { email: string }) => {
|
||||
const { data } = await apiRequest.post("/api/v1/signup/email/signup", {
|
||||
email
|
||||
});
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useVerifyEmailVerificationCode = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
email,
|
||||
code
|
||||
}: {
|
||||
email: string;
|
||||
code: string;
|
||||
}) => {
|
||||
mutationFn: async ({ email, code }: { email: string; code: string }) => {
|
||||
const { data } = await apiRequest.post("/api/v1/signup/email/verify", {
|
||||
email,
|
||||
code
|
||||
});
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useSendPasswordResetEmail = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
email
|
||||
}: {
|
||||
email: string;
|
||||
}) => {
|
||||
mutationFn: async ({ email }: { email: string }) => {
|
||||
const { data } = await apiRequest.post("/api/v1/password/email/password-reset", {
|
||||
email
|
||||
});
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useVerifyPasswordResetCode = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
email,
|
||||
code
|
||||
}: {
|
||||
email: string;
|
||||
code: string;
|
||||
}) => {
|
||||
mutationFn: async ({ email, code }: { email: string; code: string }) => {
|
||||
const { data } = await apiRequest.post("/api/v1/password/email/password-reset-verify", {
|
||||
email,
|
||||
code
|
||||
});
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const issueBackupPrivateKey = async (details: IssueBackupPrivateKeyDTO) => {
|
||||
const { data } = await apiRequest.post("/api/v1/password/backup-private-key", details);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export const getBackupEncryptedPrivateKey = async ({
|
||||
verificationToken
|
||||
@@ -207,37 +181,41 @@ export const getBackupEncryptedPrivateKey = async ({
|
||||
Authorization: `Bearer ${verificationToken}`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return data.backupPrivateKey;
|
||||
}
|
||||
};
|
||||
|
||||
export const useResetPassword = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (details: ResetPasswordDTO) => {
|
||||
const { data } = await apiRequest.post("/api/v1/password/password-reset", {
|
||||
protectedKey: details.protectedKey,
|
||||
protectedKeyIV: details.protectedKeyIV,
|
||||
protectedKeyTag: details.protectedKeyTag,
|
||||
encryptedPrivateKey: details.encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV: details.encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag: details.encryptedPrivateKeyTag,
|
||||
salt: details.salt,
|
||||
verifier: details.verifier
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${details.verificationToken}`
|
||||
const { data } = await apiRequest.post(
|
||||
"/api/v1/password/password-reset",
|
||||
{
|
||||
protectedKey: details.protectedKey,
|
||||
protectedKeyIV: details.protectedKeyIV,
|
||||
protectedKeyTag: details.protectedKeyTag,
|
||||
encryptedPrivateKey: details.encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV: details.encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag: details.encryptedPrivateKeyTag,
|
||||
salt: details.salt,
|
||||
verifier: details.verifier
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${details.verificationToken}`
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const changePassword = async (details: ChangePasswordDTO) => {
|
||||
const { data } = await apiRequest.post("/api/v1/password/change-password", details);
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export const useChangePassword = () => {
|
||||
// note: use after srp1
|
||||
@@ -246,7 +224,7 @@ export const useChangePassword = () => {
|
||||
return changePassword(details);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Refresh token is set as cookie when logged in
|
||||
// Using that we fetch the auth bearer token needed for auth calls
|
||||
@@ -263,11 +241,3 @@ export const useGetAuthToken = () =>
|
||||
onSuccess: (data) => setAuthToken(data.token),
|
||||
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 });
|
||||
@@ -22,31 +22,23 @@ import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
|
||||
import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey";
|
||||
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
import {
|
||||
useGetCommonPasswords
|
||||
} from "@app/hooks/api";
|
||||
import {
|
||||
completeAccountSignupInvite,
|
||||
verifySignupInvite
|
||||
} from "@app/hooks/api/auth/queries";
|
||||
import { completeAccountSignupInvite, verifySignupInvite } from "@app/hooks/api/auth/queries";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const client = new jsrp.client();
|
||||
|
||||
type Errors = {
|
||||
length?: string,
|
||||
upperCase?: string,
|
||||
lowerCase?: string,
|
||||
number?: string,
|
||||
specialChar?: string,
|
||||
repeatedChar?: string,
|
||||
breachedPassword?: string
|
||||
length?: string;
|
||||
upperCase?: string;
|
||||
lowerCase?: string;
|
||||
number?: string;
|
||||
specialChar?: string;
|
||||
repeatedChar?: string;
|
||||
breachedPassword?: string;
|
||||
};
|
||||
|
||||
export default function SignupInvite() {
|
||||
const { data: commonPasswords } = useGetCommonPasswords();
|
||||
|
||||
const [password, setPassword] = useState("");
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
@@ -80,10 +72,9 @@ export default function SignupInvite() {
|
||||
} else {
|
||||
setLastNameError(false);
|
||||
}
|
||||
|
||||
|
||||
errorCheck = await checkPassword({
|
||||
password,
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
|
||||
@@ -117,7 +108,7 @@ export default function SignupInvite() {
|
||||
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||
|
||||
const key = crypto.randomBytes(32);
|
||||
|
||||
|
||||
// create encrypted private key by encrypting the private
|
||||
// key with the symmetric key [key]
|
||||
const {
|
||||
@@ -128,7 +119,7 @@ export default function SignupInvite() {
|
||||
text: privateKey,
|
||||
secret: key
|
||||
});
|
||||
|
||||
|
||||
// create the protected key by encrypting the symmetric key
|
||||
// [key] with the derived key
|
||||
const {
|
||||
@@ -139,10 +130,8 @@ export default function SignupInvite() {
|
||||
text: key.toString("hex"),
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
const {
|
||||
token: jwtToken
|
||||
} = await completeAccountSignupInvite({
|
||||
|
||||
const { token: jwtToken } = await completeAccountSignupInvite({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
@@ -156,20 +145,20 @@ export default function SignupInvite() {
|
||||
salt: result.salt,
|
||||
verifier: result.verifier
|
||||
});
|
||||
|
||||
|
||||
// unset temporary signup JWT token and set JWT token
|
||||
SecurityClient.setSignupToken("");
|
||||
SecurityClient.setToken(jwtToken);
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag,
|
||||
privateKey
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
const userOrgs = await fetchOrganizations();
|
||||
const userOrgs = await fetchOrganizations();
|
||||
|
||||
const orgId = userOrgs[0]._id;
|
||||
localStorage.setItem("orgData.id", orgId);
|
||||
@@ -189,12 +178,12 @@ export default function SignupInvite() {
|
||||
|
||||
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||
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">
|
||||
<p className="text-4xl text-center font-semibold mb-6 flex justify-center text-primary-100">
|
||||
<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="mb-6 flex justify-center text-center text-4xl font-semibold text-primary-100">
|
||||
Confirm your email
|
||||
</p>
|
||||
<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
|
||||
text="Confirm Email"
|
||||
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)
|
||||
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">
|
||||
<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">
|
||||
<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="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!
|
||||
</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
|
||||
label="First Name"
|
||||
onChangeHandler={setFirstName}
|
||||
@@ -246,7 +235,7 @@ export default function SignupInvite() {
|
||||
autoComplete="given-name"
|
||||
/>
|
||||
</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
|
||||
label="Last Name"
|
||||
onChangeHandler={setLastName}
|
||||
@@ -258,14 +247,13 @@ export default function SignupInvite() {
|
||||
autoComplete="family-name"
|
||||
/>
|
||||
</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
|
||||
label="Password"
|
||||
onChangeHandler={(pass) => {
|
||||
setPassword(pass);
|
||||
checkPassword({
|
||||
password: pass,
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
}}
|
||||
@@ -277,31 +265,26 @@ export default function SignupInvite() {
|
||||
id="new-password"
|
||||
/>
|
||||
{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="mb-2 text-sm text-gray-400">Password should contain at least:</div>
|
||||
{Object.keys(errors).map((key) => {
|
||||
if (errors[key as keyof Errors]) {
|
||||
return (
|
||||
<div className="ml-1 flex flex-row items-top justify-start" key={key}>
|
||||
<div>
|
||||
<FontAwesomeIcon
|
||||
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 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>
|
||||
{Object.keys(errors).map((key) => {
|
||||
if (errors[key as keyof Errors]) {
|
||||
return (
|
||||
<div className="items-top ml-1 flex flex-row justify-start" key={key}>
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faXmark} className="text-md ml-0.5 mr-2.5 text-red" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<p className="text-sm text-gray-400">{errors[key as keyof Errors]}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
return null;
|
||||
})}
|
||||
</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
|
||||
text="Sign Up"
|
||||
onButtonPressed={() => {
|
||||
@@ -316,21 +299,21 @@ export default function SignupInvite() {
|
||||
|
||||
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||
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">
|
||||
<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">
|
||||
<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="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
|
||||
</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>
|
||||
If you get locked out of your account, your Emergency Kit is the only way to sign in.
|
||||
</div>
|
||||
<div className="mt-3">We recommend you download it and keep it somewhere safe.</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" />
|
||||
It contains your Secret Key which we cannot access or recover for you if you lose it.
|
||||
</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
|
||||
text="Download PDF"
|
||||
onButtonPressed={async () => {
|
||||
@@ -350,7 +333,7 @@ export default function SignupInvite() {
|
||||
);
|
||||
|
||||
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>
|
||||
<title>Sign Up</title>
|
||||
<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 { Button, FormControl, Input } from "@app/components/v2";
|
||||
import { useUser } from "@app/context";
|
||||
import { useGetCommonPasswords } from "@app/hooks/api";
|
||||
|
||||
type Errors = {
|
||||
tooShort?: string;
|
||||
@@ -22,7 +21,6 @@ type Errors = {
|
||||
specialChar?: string;
|
||||
repeatedChar?: string;
|
||||
isBreachedPassword?: string;
|
||||
isCommonPassword?: string;
|
||||
};
|
||||
|
||||
const schema = yup
|
||||
@@ -38,7 +36,6 @@ export const ChangePasswordSection = () => {
|
||||
const { t } = useTranslation();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { user } = useUser();
|
||||
const { data: commonPasswords } = useGetCommonPasswords();
|
||||
const { reset, control, handleSubmit } = useForm({
|
||||
defaultValues: {
|
||||
oldPassword: "",
|
||||
@@ -52,11 +49,9 @@ export const ChangePasswordSection = () => {
|
||||
const onFormSubmit = async ({ oldPassword, newPassword }: FormData) => {
|
||||
try {
|
||||
if (!user?.email) return;
|
||||
if (!commonPasswords) return;
|
||||
|
||||
const errorCheck = await checkPassword({
|
||||
password: newPassword,
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
|
||||
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
import { Button, Input } from "@app/components/v2";
|
||||
import { useGetCommonPasswords } from "@app/hooks/api";
|
||||
import { completeAccountSignup } from "@app/hooks/api/auth/queries";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import ProjectService from "@app/services/ProjectService";
|
||||
@@ -42,7 +41,6 @@ type Errors = {
|
||||
specialChar?: string;
|
||||
repeatedChar?: string;
|
||||
isBeachedPassword?: string;
|
||||
isCommonPassword?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -67,7 +65,6 @@ export const UserInfoSSOStep = ({
|
||||
setStep,
|
||||
providerAuthToken
|
||||
}: Props) => {
|
||||
const { data: commonPasswords } = useGetCommonPasswords();
|
||||
const [nameError, setNameError] = useState(false);
|
||||
const [organizationName, setOrganizationName] = useState("");
|
||||
const [organizationNameError, setOrganizationNameError] = useState(false);
|
||||
@@ -102,7 +99,6 @@ export const UserInfoSSOStep = ({
|
||||
|
||||
errorCheck = await checkPassword({
|
||||
password,
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
|
||||
@@ -277,7 +273,6 @@ export const UserInfoSSOStep = ({
|
||||
setPassword(pass);
|
||||
await checkPassword({
|
||||
password: pass,
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user