feat: refactor PAM account access endpoint to use accountPath and projectId

This commit is contained in:
Piyush Gupta
2025-11-30 01:55:24 +05:30
parent fe97767e16
commit 861003fc3f
4 changed files with 50 additions and 26 deletions

View File

@@ -106,7 +106,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
schema: {
description: "Access PAM account",
body: z.object({
accountId: z.string().uuid(),
accountPath: z.string().trim(),
projectId: z.string().uuid(),
duration: z
.string()
.min(1)
@@ -151,7 +152,9 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
actorIp: req.realIp,
actorName: `${req.auth.user.firstName ?? ""} ${req.auth.user.lastName ?? ""}`.trim(),
actorUserAgent: req.auditLogInfo.userAgent ?? "",
...req.body
accountPath: req.body.accountPath,
projectId: req.body.projectId,
duration: req.body.duration
},
req.permission
);
@@ -163,7 +166,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
event: {
type: EventType.PAM_ACCOUNT_ACCESS,
metadata: {
accountId: req.body.accountId,
accountId: response.account.id,
accountPath: req.body.accountPath,
accountName: response.account.name,
duration: req.body.duration ? new Date(req.body.duration).toISOString() : undefined
}

View File

@@ -4074,6 +4074,7 @@ interface PamAccountAccessEvent {
type: EventType.PAM_ACCOUNT_ACCESS;
metadata: {
accountId: string;
accountPath: string;
accountName: string;
duration?: string;
};

View File

@@ -487,7 +487,7 @@ export const pamAccountServiceFactory = ({
};
const access = async (
{ accountId, actorEmail, actorIp, actorName, actorUserAgent, duration }: TAccessAccountDTO,
{ accountPath, projectId, actorEmail, actorIp, actorName, actorUserAgent, duration }: TAccessAccountDTO,
actor: OrgServiceActor
) => {
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
@@ -497,8 +497,36 @@ export const pamAccountServiceFactory = ({
});
}
const account = await pamAccountDAL.findById(accountId);
if (!account) throw new NotFoundError({ message: `Account with ID '${accountId}' not found` });
const pathSegments: string[] = accountPath.split("/").filter(Boolean);
if (pathSegments.length === 0) {
throw new BadRequestError({ message: "Invalid accountPath. Path must contain at least the account name." });
}
const accountName: string = pathSegments[pathSegments.length - 1] ?? "";
const folderPathSegments: string[] = pathSegments.slice(0, -1);
const folderPath: string = folderPathSegments.length > 0 ? `/${folderPathSegments.join("/")}` : "/";
let folderId: string | null = null;
if (folderPath !== "/") {
const folder = await pamFolderDAL.findByPath(projectId, folderPath);
if (!folder) {
throw new NotFoundError({ message: `Folder at path '${folderPath}' not found` });
}
folderId = folder.id;
}
const account = await pamAccountDAL.findOne({
projectId,
folderId,
name: accountName
});
if (!account) {
throw new NotFoundError({
message: `Account with name '${accountName}' not found at path '${accountPath}'`
});
}
const resource = await pamResourceDAL.findById(account.resourceId);
if (!resource) throw new NotFoundError({ message: `Resource with ID '${account.resourceId}' not found` });
@@ -508,22 +536,16 @@ export const pamAccountServiceFactory = ({
actorAuthMethod: actor.authMethod,
actorId: actor.id,
actorOrgId: actor.orgId,
projectId: account.projectId,
projectId,
actionProjectType: ActionProjectType.PAM
});
const accountPath = await getFullPamFolderPath({
pamFolderDAL,
folderId: account.folderId,
projectId: account.projectId
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionPamAccountActions.Access,
subject(ProjectPermissionSub.PamAccounts, {
resourceName: resource.name,
accountName: account.name,
accountPath
accountPath: folderPath
})
);
@@ -533,7 +555,7 @@ export const pamAccountServiceFactory = ({
actorIp,
actorName,
actorUserAgent,
projectId: account.projectId,
projectId,
resourceName: resource.name,
resourceType: resource.resourceType,
status: PamSessionStatus.Starting,
@@ -542,11 +564,7 @@ export const pamAccountServiceFactory = ({
expiresAt: new Date(Date.now() + duration)
});
const { connectionDetails, gatewayId, resourceType } = await decryptResource(
resource,
account.projectId,
kmsService
);
const { connectionDetails, gatewayId, resourceType } = await decryptResource(resource, projectId, kmsService);
const user = await userDAL.findById(actor.id);
if (!user) throw new NotFoundError({ message: `User with ID '${actor.id}' not found` });
@@ -578,20 +596,20 @@ export const pamAccountServiceFactory = ({
const connectionCredentials = (await decryptResourceConnectionDetails({
encryptedConnectionDetails: resource.encryptedConnectionDetails,
kmsService,
projectId: account.projectId
projectId
})) as TSqlResourceConnectionDetails;
const credentials = await decryptAccountCredentials({
encryptedCredentials: account.encryptedCredentials,
kmsService,
projectId: account.projectId
projectId
});
metadata = {
username: credentials.username,
database: connectionCredentials.database,
accountName: account.name,
accountPath
accountPath: folderPath
};
}
break;
@@ -600,7 +618,7 @@ export const pamAccountServiceFactory = ({
const credentials = await decryptAccountCredentials({
encryptedCredentials: account.encryptedCredentials,
kmsService,
projectId: account.projectId
projectId
});
metadata = {
@@ -622,7 +640,7 @@ export const pamAccountServiceFactory = ({
gatewayClientPrivateKey: gatewayConnectionDetails.gateway.clientPrivateKey,
gatewayServerCertificateChain: gatewayConnectionDetails.gateway.serverCertificateChain,
relayHost: gatewayConnectionDetails.relayHost,
projectId: account.projectId,
projectId,
account,
metadata
};

View File

@@ -14,7 +14,8 @@ export type TUpdateAccountDTO = Partial<Omit<TCreateAccountDTO, "folderId" | "re
};
export type TAccessAccountDTO = {
accountId: string;
accountPath: string;
projectId: string;
actorEmail: string;
actorIp: string;
actorName: string;