mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 23:27:14 +00:00
Update MI fields numUses, numUsesLimit, ttl, added modal for delete client secret confirmation
This commit is contained in:
@@ -44,9 +44,9 @@ const packageClientSecretData = (clientSecretData: IMachineIdentityClientSecretD
|
|||||||
isActive: clientSecretData.isActive,
|
isActive: clientSecretData.isActive,
|
||||||
description: clientSecretData.description,
|
description: clientSecretData.description,
|
||||||
clientSecretPrefix: clientSecretData.clientSecretPrefix,
|
clientSecretPrefix: clientSecretData.clientSecretPrefix,
|
||||||
clientSecretUsageCount: clientSecretData.clientSecretUsageCount,
|
clientSecretNumUses: clientSecretData.clientSecretNumUses,
|
||||||
clientSecretUsageLimit: clientSecretData.clientSecretUsageLimit,
|
clientSecretNumUsesLimit: clientSecretData.clientSecretNumUsesLimit,
|
||||||
expiresAt: clientSecretData.expiresAt
|
clientSecretTTL: clientSecretData.clientSecretTTL
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,7 +121,7 @@ export const createMIClientSecret = async (req: Request, res: Response) => {
|
|||||||
body: {
|
body: {
|
||||||
description,
|
description,
|
||||||
ttl,
|
ttl,
|
||||||
usageLimit
|
numUsesLimit
|
||||||
}
|
}
|
||||||
} = await validateRequest(reqValidator.CreateClientSecretV3, req);
|
} = await validateRequest(reqValidator.CreateClientSecretV3, req);
|
||||||
|
|
||||||
@@ -145,11 +145,6 @@ export const createMIClientSecret = async (req: Request, res: Response) => {
|
|||||||
message: "Failed to create client secret for more privileged MI"
|
message: "Failed to create client secret for more privileged MI"
|
||||||
});
|
});
|
||||||
|
|
||||||
let expiresAt;
|
|
||||||
if (ttl > 0) {
|
|
||||||
expiresAt = new Date(new Date().getTime() + ttl * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientSecret = crypto.randomBytes(32).toString("hex");
|
const clientSecret = crypto.randomBytes(32).toString("hex");
|
||||||
const clientSecretHash = await bcrypt.hash(clientSecret, await getSaltRounds());
|
const clientSecretHash = await bcrypt.hash(clientSecret, await getSaltRounds());
|
||||||
|
|
||||||
@@ -159,10 +154,10 @@ export const createMIClientSecret = async (req: Request, res: Response) => {
|
|||||||
description,
|
description,
|
||||||
clientSecretPrefix: clientSecret.slice(0, 4),
|
clientSecretPrefix: clientSecret.slice(0, 4),
|
||||||
clientSecretHash,
|
clientSecretHash,
|
||||||
clientSecretUsageCount: 0,
|
clientSecretNumUses: 0,
|
||||||
clientSecretUsageLimit: usageLimit,
|
clientSecretNumUsesLimit: numUsesLimit,
|
||||||
|
clientSecretTTL: ttl,
|
||||||
accessTokenVersion: 1,
|
accessTokenVersion: 1,
|
||||||
expiresAt
|
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
await EEAuditLogService.createAuditLog(
|
await EEAuditLogService.createAuditLog(
|
||||||
@@ -294,27 +289,30 @@ export const loginMI = async (req: Request, res: Response) => {
|
|||||||
if (!validatedClientSecretDatum) throw UnauthorizedRequestError();
|
if (!validatedClientSecretDatum) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
expiresAt,
|
clientSecretTTL,
|
||||||
clientSecretUsageCount,
|
clientSecretNumUses,
|
||||||
clientSecretUsageLimit
|
clientSecretNumUsesLimit,
|
||||||
} = validatedClientSecretDatum;
|
} = validatedClientSecretDatum;
|
||||||
|
|
||||||
if (expiresAt && new Date(expiresAt) < new Date()) {
|
if (clientSecretTTL > 0) {
|
||||||
// client secret expired
|
const expiresAt = new Date(new Date().getTime() + clientSecretTTL * 1000);
|
||||||
await MachineIdentityClientSecretData.findByIdAndUpdate(
|
|
||||||
validatedClientSecretDatum._id,
|
|
||||||
{
|
|
||||||
isActive: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
throw UnauthorizedRequestError();
|
if (expiresAt < new Date()) {
|
||||||
|
await MachineIdentityClientSecretData.findByIdAndUpdate(
|
||||||
|
validatedClientSecretDatum._id,
|
||||||
|
{
|
||||||
|
isActive: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientSecretUsageLimit > 0 && clientSecretUsageCount === clientSecretUsageLimit) {
|
if (clientSecretNumUses > 0 && clientSecretNumUses === clientSecretNumUsesLimit) {
|
||||||
// number of times client secret can be used for
|
// number of times client secret can be used for
|
||||||
// a login operation reached
|
// a login operation reached
|
||||||
await MachineIdentityClientSecretData.findByIdAndUpdate(
|
await MachineIdentityClientSecretData.findByIdAndUpdate(
|
||||||
@@ -334,7 +332,7 @@ export const loginMI = async (req: Request, res: Response) => {
|
|||||||
await MachineIdentityClientSecretData.findByIdAndUpdate(
|
await MachineIdentityClientSecretData.findByIdAndUpdate(
|
||||||
validatedClientSecretDatum._id,
|
validatedClientSecretDatum._id,
|
||||||
{
|
{
|
||||||
$inc: { clientSecretUsageCount: 1 }
|
$inc: { clientSecretNumUses: 1 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ export interface IMachineIdentityClientSecretData extends Document {
|
|||||||
clientSecretPrefix: string;
|
clientSecretPrefix: string;
|
||||||
clientSecretHash: string;
|
clientSecretHash: string;
|
||||||
clientSecretLastUsed?: Date;
|
clientSecretLastUsed?: Date;
|
||||||
clientSecretUsageCount: number;
|
clientSecretNumUses: number;
|
||||||
clientSecretUsageLimit: number;
|
clientSecretNumUsesLimit: number;
|
||||||
|
clientSecretTTL: number;
|
||||||
accessTokenVersion: number;
|
accessTokenVersion: number;
|
||||||
expiresAt?: Date;
|
updatedAt: Date;
|
||||||
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const machineIdentityClientSecretDataSchema = new Schema(
|
const machineIdentityClientSecretDataSchema = new Schema(
|
||||||
@@ -42,29 +44,30 @@ const machineIdentityClientSecretDataSchema = new Schema(
|
|||||||
type: Date,
|
type: Date,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
clientSecretUsageCount: {
|
clientSecretNumUses: {
|
||||||
// number of times client secret has been used
|
// number of times client secret has been used
|
||||||
// in login operation
|
// in login operation
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
clientSecretUsageLimit: {
|
clientSecretNumUsesLimit: {
|
||||||
// number of times client secret can be used for
|
// number of times client secret can be used for
|
||||||
// a login operation
|
// a login operation
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0, // default: used as many times as needed
|
default: 0, // default: used as many times as needed
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
clientSecretTTL: {
|
||||||
|
type: Number,
|
||||||
|
default: 0, // default: does not expire
|
||||||
|
required: true
|
||||||
|
},
|
||||||
accessTokenVersion: {
|
accessTokenVersion: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1,
|
default: 1,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
expiresAt: {
|
|
||||||
type: Date,
|
|
||||||
required: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true
|
timestamps: true
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const CreateClientSecretV3 = z.object({
|
|||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
description: z.string().trim().default(""),
|
description: z.string().trim().default(""),
|
||||||
usageLimit: z.number().min(0).default(0),
|
numUsesLimit: z.number().min(0).default(0),
|
||||||
ttl: z.number().min(0).default(0),
|
ttl: z.number().min(0).default(0),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ export type MachineIdentityClientSecret = {
|
|||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
clientSecretPrefix: string;
|
clientSecretPrefix: string;
|
||||||
clientSecretUsageCount: number;
|
clientSecretNumUses: number;
|
||||||
clientSecretUsageLimit: number;
|
clientSecretNumUsesLimit: number;
|
||||||
expiresAt: string;
|
clientSecretTTL: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MachineMembershipOrg = {
|
export type MachineMembershipOrg = {
|
||||||
@@ -78,8 +80,8 @@ export type CreateMachineIdentityClientSecretRes = {
|
|||||||
machineIdentity: string;
|
machineIdentity: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
clientSecretUsageCount: number;
|
clientSecretNumUses: number;
|
||||||
clientSecretUsageLimit: number;
|
clientSecretNumUsesLimit: number;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+78
-10
@@ -10,13 +10,13 @@ import * as yup from "yup";
|
|||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
FormControl,
|
FormControl,
|
||||||
IconButton,
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
ModalContent
|
ModalContent,
|
||||||
,
|
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableSkeleton,
|
TableSkeleton,
|
||||||
@@ -41,12 +41,20 @@ const schema = yup.object({
|
|||||||
export type FormData = yup.InferType<typeof schema>;
|
export type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
popUp: UsePopUpState<["clientSecret"]>;
|
popUp: UsePopUpState<["clientSecret", "deleteClientSecret"]>;
|
||||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["clientSecret"]>, state?: boolean) => void;
|
handlePopUpOpen: (
|
||||||
|
popUpName: keyof UsePopUpState<["deleteClientSecret"]>,
|
||||||
|
data?: {
|
||||||
|
clientSecretPrefix: string;
|
||||||
|
clientSecretId: string;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["clientSecret", "deleteClientSecret"]>, state?: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateClientSecretModal = ({
|
export const CreateClientSecretModal = ({
|
||||||
popUp,
|
popUp,
|
||||||
|
handlePopUpOpen,
|
||||||
handlePopUpToggle
|
handlePopUpToggle
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -120,6 +128,43 @@ export const CreateClientSecretModal = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onDeleteClientSecretSubmit = async ({
|
||||||
|
clientSecretId,
|
||||||
|
clientSecretPrefix
|
||||||
|
}: {
|
||||||
|
clientSecretId: string;
|
||||||
|
clientSecretPrefix: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (!popUpData?.machineId) return;
|
||||||
|
|
||||||
|
await deleteClientSecretMutateAsync({
|
||||||
|
machineId: popUpData.machineId,
|
||||||
|
clientSecretId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (token.startsWith(clientSecretPrefix)) {
|
||||||
|
reset();
|
||||||
|
setToken("");
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpToggle("deleteClientSecret", false);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted client secret",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete client secret",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const hasToken = Boolean(token);
|
const hasToken = Boolean(token);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -238,20 +283,24 @@ export const CreateClientSecretModal = ({
|
|||||||
data.map(({
|
data.map(({
|
||||||
_id,
|
_id,
|
||||||
description,
|
description,
|
||||||
machineIdentity,
|
clientSecretTTL,
|
||||||
expiresAt,
|
|
||||||
clientSecretPrefix
|
clientSecretPrefix
|
||||||
}) => {
|
}) => {
|
||||||
|
let expiresAt;
|
||||||
|
if (clientSecretTTL > 0) {
|
||||||
|
expiresAt = new Date(new Date().getTime() + clientSecretTTL * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr className="h-10" key={`mi-client-secret-${_id}`}>
|
<Tr className="h-10" key={`mi-client-secret-${_id}`}>
|
||||||
<Td>{description === "" ? "-" : description}</Td>
|
<Td>{description === "" ? "-" : description}</Td>
|
||||||
<Td>{expiresAt ? format(new Date(expiresAt), "yyyy-MM-dd") : "-"}</Td>
|
<Td>{expiresAt ? format(expiresAt, "yyyy-MM-dd") : "-"}</Td>
|
||||||
<Td>{`${clientSecretPrefix}************`}</Td>
|
<Td>{`${clientSecretPrefix}************`}</Td>
|
||||||
<Td className="flex">
|
<Td className="flex">
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={async () => {
|
onClick={() => {
|
||||||
await deleteClientSecretMutateAsync({
|
handlePopUpOpen("deleteClientSecret", {
|
||||||
machineId: machineIdentity,
|
clientSecretPrefix,
|
||||||
clientSecretId: _id
|
clientSecretId: _id
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -277,6 +326,25 @@ export const CreateClientSecretModal = ({
|
|||||||
</TBody>
|
</TBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteClientSecret.isOpen}
|
||||||
|
title={`Are you sure want to delete the client secret ${
|
||||||
|
(popUp?.deleteClientSecret?.data as { clientSecretPrefix: string })?.clientSecretPrefix || ""
|
||||||
|
}************?`}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteClientSecret", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={() => {
|
||||||
|
const deleteClientSecretData = (popUp?.deleteClientSecret?.data as {
|
||||||
|
clientSecretId: string;
|
||||||
|
clientSecretPrefix: string;
|
||||||
|
});
|
||||||
|
|
||||||
|
return onDeleteClientSecretSubmit({
|
||||||
|
clientSecretId: deleteClientSecretData.clientSecretId,
|
||||||
|
clientSecretPrefix: deleteClientSecretData.clientSecretPrefix
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
+2
@@ -24,6 +24,7 @@ export const MachineIdentitySection = withPermission(
|
|||||||
"machineIdentity",
|
"machineIdentity",
|
||||||
"deleteMachineIdentity",
|
"deleteMachineIdentity",
|
||||||
"clientSecret",
|
"clientSecret",
|
||||||
|
"deleteClientSecret",
|
||||||
"upgradePlan"
|
"upgradePlan"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ export const MachineIdentitySection = withPermission(
|
|||||||
/>
|
/>
|
||||||
<CreateClientSecretModal
|
<CreateClientSecretModal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
|
handlePopUpOpen={handlePopUpOpen}
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
/>
|
/>
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
|
|||||||
Reference in New Issue
Block a user