grant endpoints

This commit is contained in:
x032205
2025-12-06 20:25:42 -05:00
parent c60c397e6a
commit 68eef39dd7
6 changed files with 156 additions and 5 deletions

View File

@@ -19,7 +19,8 @@ export const registerApprovalPolicyEndpoints = <P extends TApprovalPolicy>({
updatePolicySchema,
policyResponseSchema,
createRequestSchema,
requestResponseSchema
requestResponseSchema,
grantResponseSchema
}: {
server: FastifyZodProvider;
policyType: ApprovalPolicyType;
@@ -38,6 +39,7 @@ export const registerApprovalPolicyEndpoints = <P extends TApprovalPolicy>({
policyResponseSchema: z.ZodTypeAny;
createRequestSchema: z.ZodType<TCreateRequestDTO>;
requestResponseSchema: z.ZodTypeAny;
grantResponseSchema: z.ZodTypeAny;
}) => {
// Policies
server.route({
@@ -365,4 +367,93 @@ export const registerApprovalPolicyEndpoints = <P extends TApprovalPolicy>({
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 };
}
});
};

View File

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

View File

@@ -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;

View File

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

View File

@@ -54,7 +54,7 @@ export interface TCreateRequestDTO {
projectId: TApprovalRequest["projectId"];
requestData: TApprovalRequest["requestData"]["requestData"];
justification?: TApprovalRequest["justification"];
requestDuration?: string;
requestDuration?: string | null;
}
// Factory

View File

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