Add access request note and change secret request to change request

This commit is contained in:
carlosmonastyrski
2025-04-10 20:10:38 -03:00
parent 700efc9b6d
commit 230b44fca1
12 changed files with 73 additions and 11 deletions

View File

@@ -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");
});
}
}

View File

@@ -17,7 +17,8 @@ export const AccessApprovalRequestsSchema = z.object({
permissions: z.unknown(),
createdAt: 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>;

View File

@@ -22,7 +22,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
body: z.object({
permissions: z.any().array(),
isTemporary: z.boolean(),
temporaryRange: z.string().optional()
temporaryRange: z.string().optional(),
note: z.string().optional()
}),
querystring: z.object({
projectSlug: z.string().trim()
@@ -43,7 +44,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
actorOrgId: req.permission.orgId,
projectSlug: req.query.projectSlug,
temporaryRange: req.body.temporaryRange,
isTemporary: req.body.isTemporary
isTemporary: req.body.isTemporary,
note: req.body.note
});
return { approval: request };
}

View File

@@ -94,7 +94,8 @@ export const accessApprovalRequestServiceFactory = ({
actor,
actorOrgId,
actorAuthMethod,
projectSlug
projectSlug,
note
}: TCreateAccessApprovalRequestDTO) => {
const cfg = getConfig();
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
@@ -209,7 +210,8 @@ export const accessApprovalRequestServiceFactory = ({
requestedByUserId: actorId,
temporaryRange: temporaryRange || null,
permissions: JSON.stringify(requestedPermissions),
isTemporary
isTemporary,
note: note || null
},
tx
);
@@ -232,7 +234,8 @@ export const accessApprovalRequestServiceFactory = ({
secretPath,
environment: envSlug,
permissions: accessTypes,
approvalUrl
approvalUrl,
note
}
}
});
@@ -252,7 +255,8 @@ export const accessApprovalRequestServiceFactory = ({
secretPath,
environment: envSlug,
permissions: accessTypes,
approvalUrl
approvalUrl,
note
},
template: SmtpTemplates.AccessApprovalRequest
});

View File

@@ -24,6 +24,7 @@ export type TCreateAccessApprovalRequestDTO = {
permissions: unknown;
isTemporary: boolean;
temporaryRange?: string;
note?: string;
} & Omit<TProjectPermission, "projectId">;
export type TListApprovalRequestsDTO = {

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(", ")}
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 = [
{

View File

@@ -76,5 +76,6 @@ export type TSlackNotification =
projectName: string;
permissions: string[];
approvalUrl: string;
note?: string;
};
};

View File

@@ -40,6 +40,9 @@
{{/each}}
</ul>
</p>
{{#if note}}
<p>User Note: "{{note}}"</p>
{{/if}}
<p>
View the request and approve or deny it

View File

@@ -79,6 +79,8 @@ export type TAccessApprovalRequest = {
member: string;
status: string;
}[];
note?: string;
};
export type TAccessApproval = {
@@ -119,6 +121,7 @@ export type TProjectUserPrivilege = {
export type TCreateAccessRequestDTO = {
projectSlug: string;
note?: string;
} & Omit<TProjectUserPrivilege, "id" | "createdAt" | "updatedAt" | "slug" | "projectMembershipId">;
export type TGetAccessApprovalRequestsDTO = {

View File

@@ -67,7 +67,8 @@ const secretPermissionSchema = z.object({
z.object({
isTemporary: z.literal(false)
})
])
]),
note: z.string().optional()
});
type TSecretPermissionForm = z.infer<typeof secretPermissionSchema>;
export const SpecificPrivilegeSecretForm = ({
@@ -231,7 +232,8 @@ export const SpecificPrivilegeSecretForm = ({
action,
subject: [ProjectPermissionSub.Secrets],
conditions
}))
})),
note: data.note
});
createNotification({
@@ -541,6 +543,18 @@ export const SpecificPrivilegeSecretForm = ({
)}
</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 && (
<Button
type="submit"

View File

@@ -64,7 +64,7 @@ export const SecretApprovalsPage = () => {
<Tabs defaultValue={defaultTab}>
<TabList>
<Tab value={TabSection.SecretApprovalRequests}>
Secret Requests
Change Requests
{Boolean(secretApprovalReqCount?.open) && (
<Badge className="ml-2">{secretApprovalReqCount?.open}</Badge>
)}

View File

@@ -136,6 +136,13 @@ export const ReviewAccessRequestModal = ({
<span className="font-bold">Access Type: </span>
<span>{getAccessLabel()}</span>
</div>
{request.note && (
<div className="mt-1">
<span className="font-bold">User Note: </span>
<span>{request.note}</span>
</div>
)}
</div>
<div className="space-x-2">