Merge pull request #3395 from Infisical/feat/addCommentToAccessRequests

Add access request note and change secret request to change request
This commit is contained in:
carlosmonastyrski
2025-04-11 15:57:38 -03:00
committed by GitHub
12 changed files with 73 additions and 11 deletions
@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasCol = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "note");
if (!hasCol) {
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
t.string("note").nullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasCol = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "note");
if (hasCol) {
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
t.dropColumn("note");
});
}
}
@@ -17,7 +17,8 @@ export const AccessApprovalRequestsSchema = z.object({
permissions: z.unknown(), permissions: z.unknown(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
requestedByUserId: z.string().uuid() requestedByUserId: z.string().uuid(),
note: z.string().nullable().optional()
}); });
export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>; export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>;
@@ -22,7 +22,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
body: z.object({ body: z.object({
permissions: z.any().array(), permissions: z.any().array(),
isTemporary: z.boolean(), isTemporary: z.boolean(),
temporaryRange: z.string().optional() temporaryRange: z.string().optional(),
note: z.string().max(255).optional()
}), }),
querystring: z.object({ querystring: z.object({
projectSlug: z.string().trim() projectSlug: z.string().trim()
@@ -43,7 +44,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
projectSlug: req.query.projectSlug, projectSlug: req.query.projectSlug,
temporaryRange: req.body.temporaryRange, temporaryRange: req.body.temporaryRange,
isTemporary: req.body.isTemporary isTemporary: req.body.isTemporary,
note: req.body.note
}); });
return { approval: request }; return { approval: request };
} }
@@ -94,7 +94,8 @@ export const accessApprovalRequestServiceFactory = ({
actor, actor,
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
projectSlug projectSlug,
note
}: TCreateAccessApprovalRequestDTO) => { }: TCreateAccessApprovalRequestDTO) => {
const cfg = getConfig(); const cfg = getConfig();
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
@@ -209,7 +210,8 @@ export const accessApprovalRequestServiceFactory = ({
requestedByUserId: actorId, requestedByUserId: actorId,
temporaryRange: temporaryRange || null, temporaryRange: temporaryRange || null,
permissions: JSON.stringify(requestedPermissions), permissions: JSON.stringify(requestedPermissions),
isTemporary isTemporary,
note: note || null
}, },
tx tx
); );
@@ -232,7 +234,8 @@ export const accessApprovalRequestServiceFactory = ({
secretPath, secretPath,
environment: envSlug, environment: envSlug,
permissions: accessTypes, permissions: accessTypes,
approvalUrl approvalUrl,
note
} }
} }
}); });
@@ -252,7 +255,8 @@ export const accessApprovalRequestServiceFactory = ({
secretPath, secretPath,
environment: envSlug, environment: envSlug,
permissions: accessTypes, permissions: accessTypes,
approvalUrl approvalUrl,
note
}, },
template: SmtpTemplates.AccessApprovalRequest template: SmtpTemplates.AccessApprovalRequest
}); });
@@ -24,6 +24,7 @@ export type TCreateAccessApprovalRequestDTO = {
permissions: unknown; permissions: unknown;
isTemporary: boolean; isTemporary: boolean;
temporaryRange?: string; temporaryRange?: string;
note?: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TListApprovalRequestsDTO = { export type TListApprovalRequestsDTO = {
+6 -1
View File
@@ -87,7 +87,12 @@ View the complete details <${appCfg.SITE_URL}/secret-manager/${payload.projectId
The following permissions are requested: ${payload.permissions.join(", ")} The following permissions are requested: ${payload.permissions.join(", ")}
View the request and approve or deny it <${payload.approvalUrl}|here>.`; View the request and approve or deny it <${payload.approvalUrl}|here>.${
payload.note
? `
User Note: ${payload.note}`
: ""
}`;
const payloadBlocks = [ const payloadBlocks = [
{ {
@@ -76,5 +76,6 @@ export type TSlackNotification =
projectName: string; projectName: string;
permissions: string[]; permissions: string[];
approvalUrl: string; approvalUrl: string;
note?: string;
}; };
}; };
@@ -40,6 +40,9 @@
{{/each}} {{/each}}
</ul> </ul>
</p> </p>
{{#if note}}
<p>User Note: "{{note}}"</p>
{{/if}}
<p> <p>
View the request and approve or deny it View the request and approve or deny it
@@ -79,6 +79,8 @@ export type TAccessApprovalRequest = {
member: string; member: string;
status: string; status: string;
}[]; }[];
note?: string;
}; };
export type TAccessApproval = { export type TAccessApproval = {
@@ -119,6 +121,7 @@ export type TProjectUserPrivilege = {
export type TCreateAccessRequestDTO = { export type TCreateAccessRequestDTO = {
projectSlug: string; projectSlug: string;
note?: string;
} & Omit<TProjectUserPrivilege, "id" | "createdAt" | "updatedAt" | "slug" | "projectMembershipId">; } & Omit<TProjectUserPrivilege, "id" | "createdAt" | "updatedAt" | "slug" | "projectMembershipId">;
export type TGetAccessApprovalRequestsDTO = { export type TGetAccessApprovalRequestsDTO = {
@@ -67,7 +67,8 @@ const secretPermissionSchema = z.object({
z.object({ z.object({
isTemporary: z.literal(false) isTemporary: z.literal(false)
}) })
]) ]),
note: z.string().optional()
}); });
type TSecretPermissionForm = z.infer<typeof secretPermissionSchema>; type TSecretPermissionForm = z.infer<typeof secretPermissionSchema>;
export const SpecificPrivilegeSecretForm = ({ export const SpecificPrivilegeSecretForm = ({
@@ -231,7 +232,8 @@ export const SpecificPrivilegeSecretForm = ({
action, action,
subject: [ProjectPermissionSub.Secrets], subject: [ProjectPermissionSub.Secrets],
conditions conditions
})) })),
note: data.note
}); });
createNotification({ createNotification({
@@ -541,6 +543,18 @@ export const SpecificPrivilegeSecretForm = ({
)} )}
</div> </div>
</div> </div>
<div className="mb-4 flex w-full">
<Controller
control={privilegeForm.control}
name="note"
render={({ field }) => (
<div className="w-full">
<FormLabel label="Note" className="mb-2" />
<Input {...field} isDisabled={isMemberEditDisabled} maxLength={255} />
</div>
)}
/>
</div>
{!!policies && ( {!!policies && (
<Button <Button
type="submit" type="submit"
@@ -64,7 +64,7 @@ export const SecretApprovalsPage = () => {
<Tabs defaultValue={defaultTab}> <Tabs defaultValue={defaultTab}>
<TabList> <TabList>
<Tab value={TabSection.SecretApprovalRequests}> <Tab value={TabSection.SecretApprovalRequests}>
Secret Requests Change Requests
{Boolean(secretApprovalReqCount?.open) && ( {Boolean(secretApprovalReqCount?.open) && (
<Badge className="ml-2">{secretApprovalReqCount?.open}</Badge> <Badge className="ml-2">{secretApprovalReqCount?.open}</Badge>
)} )}
@@ -136,6 +136,13 @@ export const ReviewAccessRequestModal = ({
<span className="font-bold">Access Type: </span> <span className="font-bold">Access Type: </span>
<span>{getAccessLabel()}</span> <span>{getAccessLabel()}</span>
</div> </div>
{request.note && (
<div className="mt-1">
<span className="font-bold">User Note: </span>
<span>{request.note}</span>
</div>
)}
</div> </div>
<div className="space-x-2"> <div className="space-x-2">