feat(pam): PAM Platform V1

This commit is contained in:
x032205
2025-09-27 01:57:43 -04:00
parent 270d8237fe
commit 4b1664c30f
141 changed files with 9327 additions and 46 deletions
@@ -0,0 +1,9 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TPamFolderDALFactory = ReturnType<typeof pamFolderDALFactory>;
export const pamFolderDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.PamFolder);
return { ...orm };
};
@@ -0,0 +1,33 @@
import { TPamFolderDALFactory } from "./pam-folder-dal";
type GetFullFolderPath = {
pamFolderDAL: Pick<TPamFolderDALFactory, "find">;
folderId?: string | null;
projectId: string;
};
export const getFullPamFolderPath = async ({
pamFolderDAL,
folderId,
projectId
}: GetFullFolderPath): Promise<string> => {
if (!folderId) return "/";
const folders = await pamFolderDAL.find({ projectId });
const folderMap = new Map(folders.map((folder) => [folder.id, folder]));
if (!folderMap.has(folderId)) return "";
const path: string[] = [];
let currentFolderId: string | null | undefined = folderId;
while (currentFolderId) {
const folder = folderMap.get(currentFolderId);
if (!folder) break;
path.unshift(folder.name);
currentFolderId = folder.parentId;
}
return `/${path.join("/")}`;
};
@@ -0,0 +1,151 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType, TPamFolders } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
import { TLicenseServiceFactory } from "../license/license-service";
import { TPamFolderDALFactory } from "./pam-folder-dal";
import { TCreateFolderDTO, TUpdateFolderDTO } from "./pam-folder-types";
type TPamFolderServiceFactoryDep = {
pamFolderDAL: TPamFolderDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
export type TPamFolderServiceFactory = ReturnType<typeof pamFolderServiceFactory>;
export const pamFolderServiceFactory = ({
pamFolderDAL,
permissionService,
licenseService
}: TPamFolderServiceFactoryDep) => {
const createFolder = async ({ name, description, parentId, projectId }: TCreateFolderDTO, actor: OrgServiceActor) => {
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
if (!orgLicensePlan.pam) {
throw new BadRequestError({
message: "PAM operation failed due to organization plan restrictions."
});
}
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorAuthMethod: actor.authMethod,
actorId: actor.id,
actorOrgId: actor.orgId,
projectId,
actionProjectType: ActionProjectType.PAM
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.PamFolders);
if (parentId) {
if (!(await pamFolderDAL.findOne({ id: parentId, projectId }))) {
throw new NotFoundError({
message: `Parent folder '${parentId}' not found for project '${projectId}'`
});
}
}
const existingFolder = await pamFolderDAL.findOne({
name,
parentId: parentId || null,
projectId
});
if (existingFolder) {
throw new BadRequestError({
message: `Folder with name '${name}' already exists for this parent`
});
}
const folder = await pamFolderDAL.create({
name,
description: description ?? null,
parentId: parentId || null,
projectId
});
return folder;
};
const updateFolder = async ({ id, name, description }: TUpdateFolderDTO, actor: OrgServiceActor) => {
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
if (!orgLicensePlan.pam) {
throw new BadRequestError({
message: "PAM operation failed due to organization plan restrictions."
});
}
const folder = await pamFolderDAL.findById(id);
if (!folder) throw new NotFoundError({ message: `Folder with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorAuthMethod: actor.authMethod,
actorId: actor.id,
actorOrgId: actor.orgId,
projectId: folder.projectId,
actionProjectType: ActionProjectType.PAM
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PamFolders);
const updateDoc: Partial<TPamFolders> = {};
if (name !== undefined) {
updateDoc.name = name;
}
if (description !== undefined) {
updateDoc.description = description;
}
if (name && name !== folder.name) {
const existingFolder = await pamFolderDAL.findOne({
name,
parentId: folder.parentId || null,
projectId: folder.projectId
});
if (existingFolder) {
throw new BadRequestError({
message: `Folder with name '${name}' already exists for this parent`
});
}
}
if (Object.keys(updateDoc).length === 0) {
return folder;
}
const updatedFolder = await pamFolderDAL.updateById(id, updateDoc);
return updatedFolder;
};
const deleteFolder = async (id: string, actor: OrgServiceActor) => {
const folder = await pamFolderDAL.findById(id);
if (!folder) throw new NotFoundError({ message: `Folder with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorAuthMethod: actor.authMethod,
actorId: actor.id,
actorOrgId: actor.orgId,
projectId: folder.projectId,
actionProjectType: ActionProjectType.PAM
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.PamFolders);
const deletedFolder = await pamFolderDAL.deleteById(id);
return deletedFolder;
};
return { createFolder, updateFolder, deleteFolder };
};
@@ -0,0 +1,13 @@
// DTOs
export interface TCreateFolderDTO {
projectId: string;
parentId?: string | null;
name: string;
description?: string | null;
}
export interface TUpdateFolderDTO {
id: string;
name?: string;
description?: string | null;
}