diff --git a/backend/src/server/routes/v1/approval-policy-routers/approval-policy-endpoints.ts b/backend/src/server/routes/v1/approval-policy-routers/approval-policy-endpoints.ts index 4a84f40ab..dc3363497 100644 --- a/backend/src/server/routes/v1/approval-policy-routers/approval-policy-endpoints.ts +++ b/backend/src/server/routes/v1/approval-policy-routers/approval-policy-endpoints.ts @@ -19,7 +19,8 @@ export const registerApprovalPolicyEndpoints =
({ updatePolicySchema, policyResponseSchema, createRequestSchema, - requestResponseSchema + requestResponseSchema, + grantResponseSchema }: { server: FastifyZodProvider; policyType: ApprovalPolicyType; @@ -38,6 +39,7 @@ export const registerApprovalPolicyEndpoints =
({
policyResponseSchema: z.ZodTypeAny;
createRequestSchema: z.ZodType ({
return { request };
}
});
+
+ // Grants
+ server.route({
+ method: "GET",
+ url: "/grants",
+ config: {
+ rateLimit: readLimit
+ },
+ schema: {
+ description: "List approval grants",
+ querystring: z.object({
+ projectId: z.string().uuid()
+ }),
+ response: {
+ 200: z.object({
+ grants: z.array(grantResponseSchema)
+ })
+ }
+ },
+ onRequest: verifyAuth([AuthMode.JWT]),
+ handler: async (req) => {
+ const { grants } = await server.services.approvalPolicy.listGrants(
+ policyType,
+ req.query.projectId,
+ req.permission
+ );
+
+ // TODO(andrey): Audit log
+
+ return { grants };
+ }
+ });
+
+ server.route({
+ method: "GET",
+ url: "/grants/:grantId",
+ config: {
+ rateLimit: readLimit
+ },
+ schema: {
+ description: "Get approval grant",
+ params: z.object({
+ grantId: z.string().uuid()
+ }),
+ response: {
+ 200: z.object({
+ grant: grantResponseSchema
+ })
+ }
+ },
+ onRequest: verifyAuth([AuthMode.JWT]),
+ handler: async (req) => {
+ const { grant } = await server.services.approvalPolicy.getGrantById(req.params.grantId, req.permission);
+
+ // TODO(andrey): Audit log
+
+ return { grant };
+ }
+ });
+
+ server.route({
+ method: "POST",
+ url: "/grants/:grantId/revoke",
+ config: {
+ rateLimit: writeLimit
+ },
+ schema: {
+ description: "Revoke approval grant",
+ params: z.object({
+ grantId: z.string().uuid()
+ }),
+ body: z.object({
+ revocationReason: z.string().optional()
+ }),
+ response: {
+ 200: z.object({
+ grant: grantResponseSchema
+ })
+ }
+ },
+ onRequest: verifyAuth([AuthMode.JWT]),
+ handler: async (req) => {
+ const { grant } = await server.services.approvalPolicy.revokeGrant(req.params.grantId, req.body, req.permission);
+
+ // TODO(andrey): Audit log
+
+ return { grant };
+ }
+ });
};
diff --git a/backend/src/server/routes/v1/approval-policy-routers/index.ts b/backend/src/server/routes/v1/approval-policy-routers/index.ts
index e1c1a3644..c848e2e47 100644
--- a/backend/src/server/routes/v1/approval-policy-routers/index.ts
+++ b/backend/src/server/routes/v1/approval-policy-routers/index.ts
@@ -3,6 +3,7 @@ import {
CreatePamAccessPolicySchema,
CreatePamAccessRequestSchema,
PamAccessPolicySchema,
+ PamAccessRequestGrantSchema,
PamAccessRequestSchema,
UpdatePamAccessPolicySchema
} from "@app/services/approval-policy/pam-access/pam-access-policy-schemas";
@@ -21,7 +22,8 @@ export const APPROVAL_POLICY_REGISTER_ROUTER_MAP: Record<
updatePolicySchema: UpdatePamAccessPolicySchema,
policyResponseSchema: PamAccessPolicySchema,
createRequestSchema: CreatePamAccessRequestSchema,
- requestResponseSchema: PamAccessRequestSchema
+ requestResponseSchema: PamAccessRequestSchema,
+ grantResponseSchema: PamAccessRequestGrantSchema
});
}
};
diff --git a/backend/src/services/approval-policy/approval-policy-schemas.ts b/backend/src/services/approval-policy/approval-policy-schemas.ts
index b848eb004..3c47b2c6b 100644
--- a/backend/src/services/approval-policy/approval-policy-schemas.ts
+++ b/backend/src/services/approval-policy/approval-policy-schemas.ts
@@ -3,6 +3,7 @@ import { z } from "zod";
import {
ApprovalPoliciesSchema,
ApprovalRequestApprovalsSchema,
+ ApprovalRequestGrantsSchema,
ApprovalRequestsSchema,
ApprovalRequestStepsSchema
} from "@app/db/schemas";
@@ -89,3 +90,6 @@ export const BaseCreateApprovalRequestSchema = z.object({
.nullable()
.optional()
});
+
+// Grants
+export const BaseApprovalRequestGrantSchema = ApprovalRequestGrantsSchema;
diff --git a/backend/src/services/approval-policy/approval-policy-service.ts b/backend/src/services/approval-policy/approval-policy-service.ts
index 3c04a7866..4cb1d147c 100644
--- a/backend/src/services/approval-policy/approval-policy-service.ts
+++ b/backend/src/services/approval-policy/approval-policy-service.ts
@@ -1,7 +1,7 @@
import { ActionProjectType, ProjectMembershipRole, TApprovalPolicies, TApprovalRequests } from "@app/db/schemas";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
-import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
+import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { OrgServiceActor } from "@app/lib/types";
import { TNotificationServiceFactory } from "@app/services/notification/notification-service";
@@ -21,6 +21,7 @@ import {
import {
ApprovalPolicyType,
ApprovalRequestApprovalDecision,
+ ApprovalRequestGrantStatus,
ApprovalRequestStatus,
ApprovalRequestStepStatus,
ApproverType
@@ -699,6 +700,50 @@ export const approvalPolicyServiceFactory = ({
return { request: { ...updatedRequest, steps } };
};
+ const listGrants = async (policyType: ApprovalPolicyType, projectId: string, actor: OrgServiceActor) => {
+ // TODO(andrey): Perm check
+
+ const grants = await approvalRequestGrantsDAL.find({ projectId, type: policyType });
+ return { grants };
+ };
+
+ const getGrantById = async (grantId: string, actor: OrgServiceActor) => {
+ // TODO(andrey): Perm check
+
+ const grant = await approvalRequestGrantsDAL.findById(grantId);
+ if (!grant) {
+ throw new NotFoundError({ message: "Grant not found" });
+ }
+
+ return { grant };
+ };
+
+ const revokeGrant = async (
+ grantId: string,
+ { revocationReason }: { revocationReason?: string },
+ actor: OrgServiceActor
+ ) => {
+ // TODO(andrey): Perm check
+
+ const grant = await approvalRequestGrantsDAL.findById(grantId);
+ if (!grant) {
+ throw new NotFoundError({ message: "Grant not found" });
+ }
+
+ if (grant.status !== ApprovalRequestGrantStatus.Active) {
+ throw new BadRequestError({ message: "Grant is not active" });
+ }
+
+ const updatedGrant = await approvalRequestGrantsDAL.updateById(grantId, {
+ status: ApprovalRequestGrantStatus.Revoked,
+ revokedAt: new Date(),
+ revokedByUserId: actor.id,
+ revocationReason
+ });
+
+ return { grant: updatedGrant };
+ };
+
return {
create,
list,
@@ -710,6 +755,9 @@ export const approvalPolicyServiceFactory = ({
getRequestById,
approveRequest,
rejectRequest,
- cancelRequest
+ cancelRequest,
+ listGrants,
+ getGrantById,
+ revokeGrant
};
};
diff --git a/backend/src/services/approval-policy/approval-policy-types.ts b/backend/src/services/approval-policy/approval-policy-types.ts
index b0f2dd530..f92451c17 100644
--- a/backend/src/services/approval-policy/approval-policy-types.ts
+++ b/backend/src/services/approval-policy/approval-policy-types.ts
@@ -54,7 +54,7 @@ export interface TCreateRequestDTO {
projectId: TApprovalRequest["projectId"];
requestData: TApprovalRequest["requestData"]["requestData"];
justification?: TApprovalRequest["justification"];
- requestDuration?: string;
+ requestDuration?: string | null;
}
// Factory
diff --git a/backend/src/services/approval-policy/pam-access/pam-access-policy-schemas.ts b/backend/src/services/approval-policy/pam-access/pam-access-policy-schemas.ts
index 7d6dad9b9..d1eb13726 100644
--- a/backend/src/services/approval-policy/pam-access/pam-access-policy-schemas.ts
+++ b/backend/src/services/approval-policy/pam-access/pam-access-policy-schemas.ts
@@ -4,6 +4,7 @@ import { ms } from "@app/lib/ms";
import {
BaseApprovalPolicySchema,
+ BaseApprovalRequestGrantSchema,
BaseApprovalRequestSchema,
BaseCreateApprovalPolicySchema,
BaseCreateApprovalRequestSchema,
@@ -79,3 +80,8 @@ export const PamAccessRequestSchema = BaseApprovalRequestSchema.extend({
export const CreatePamAccessRequestSchema = BaseCreateApprovalRequestSchema.extend({
requestData: PamAccessPolicyRequestDataSchema
});
+
+// Grants
+export const PamAccessRequestGrantSchema = BaseApprovalRequestGrantSchema.extend({
+ attributes: PamAccessPolicyRequestDataSchema
+});