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
@@ -106,7 +106,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
schema: { schema: {
description: "Access PAM account", description: "Access PAM account",
body: z.object({ body: z.object({
accountId: z.string().uuid(), accountPath: z.string().trim(),
projectId: z.string().uuid(),
duration: z duration: z
.string() .string()
.min(1) .min(1)
@@ -151,7 +152,9 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
actorIp: req.realIp, actorIp: req.realIp,
actorName: `${req.auth.user.firstName ?? ""} ${req.auth.user.lastName ?? ""}`.trim(), actorName: `${req.auth.user.firstName ?? ""} ${req.auth.user.lastName ?? ""}`.trim(),
actorUserAgent: req.auditLogInfo.userAgent ?? "", actorUserAgent: req.auditLogInfo.userAgent ?? "",
...req.body accountPath: req.body.accountPath,
projectId: req.body.projectId,
duration: req.body.duration
}, },
req.permission req.permission
); );
@@ -163,7 +166,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
event: { event: {
type: EventType.PAM_ACCOUNT_ACCESS, type: EventType.PAM_ACCOUNT_ACCESS,
metadata: { metadata: {
accountId: req.body.accountId, accountId: response.account.id,
accountPath: req.body.accountPath,
accountName: response.account.name, accountName: response.account.name,
duration: req.body.duration ? new Date(req.body.duration).toISOString() : undefined duration: req.body.duration ? new Date(req.body.duration).toISOString() : undefined
} }
@@ -4074,6 +4074,7 @@ interface PamAccountAccessEvent {
type: EventType.PAM_ACCOUNT_ACCESS; type: EventType.PAM_ACCOUNT_ACCESS;
metadata: { metadata: {
accountId: string; accountId: string;
accountPath: string;
accountName: string; accountName: string;
duration?: string; duration?: string;
}; };
@@ -487,7 +487,7 @@ export const pamAccountServiceFactory = ({
}; };
const access = async ( const access = async (
{ accountId, actorEmail, actorIp, actorName, actorUserAgent, duration }: TAccessAccountDTO, { accountPath, projectId, actorEmail, actorIp, actorName, actorUserAgent, duration }: TAccessAccountDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
const orgLicensePlan = await licenseService.getPlan(actor.orgId); const orgLicensePlan = await licenseService.getPlan(actor.orgId);
@@ -497,8 +497,36 @@ export const pamAccountServiceFactory = ({
}); });
} }
const account = await pamAccountDAL.findById(accountId); const pathSegments: string[] = accountPath.split("/").filter(Boolean);
if (!account) throw new NotFoundError({ message: `Account with ID '${accountId}' not found` }); 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); const resource = await pamResourceDAL.findById(account.resourceId);
if (!resource) throw new NotFoundError({ message: `Resource with ID '${account.resourceId}' not found` }); if (!resource) throw new NotFoundError({ message: `Resource with ID '${account.resourceId}' not found` });
@@ -508,22 +536,16 @@ export const pamAccountServiceFactory = ({
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorId: actor.id, actorId: actor.id,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
projectId: account.projectId, projectId,
actionProjectType: ActionProjectType.PAM actionProjectType: ActionProjectType.PAM
}); });
const accountPath = await getFullPamFolderPath({
pamFolderDAL,
folderId: account.folderId,
projectId: account.projectId
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionPamAccountActions.Access, ProjectPermissionPamAccountActions.Access,
subject(ProjectPermissionSub.PamAccounts, { subject(ProjectPermissionSub.PamAccounts, {
resourceName: resource.name, resourceName: resource.name,
accountName: account.name, accountName: account.name,
accountPath accountPath: folderPath
}) })
); );
@@ -533,7 +555,7 @@ export const pamAccountServiceFactory = ({
actorIp, actorIp,
actorName, actorName,
actorUserAgent, actorUserAgent,
projectId: account.projectId, projectId,
resourceName: resource.name, resourceName: resource.name,
resourceType: resource.resourceType, resourceType: resource.resourceType,
status: PamSessionStatus.Starting, status: PamSessionStatus.Starting,
@@ -542,11 +564,7 @@ export const pamAccountServiceFactory = ({
expiresAt: new Date(Date.now() + duration) expiresAt: new Date(Date.now() + duration)
}); });
const { connectionDetails, gatewayId, resourceType } = await decryptResource( const { connectionDetails, gatewayId, resourceType } = await decryptResource(resource, projectId, kmsService);
resource,
account.projectId,
kmsService
);
const user = await userDAL.findById(actor.id); const user = await userDAL.findById(actor.id);
if (!user) throw new NotFoundError({ message: `User with ID '${actor.id}' not found` }); if (!user) throw new NotFoundError({ message: `User with ID '${actor.id}' not found` });
@@ -578,20 +596,20 @@ export const pamAccountServiceFactory = ({
const connectionCredentials = (await decryptResourceConnectionDetails({ const connectionCredentials = (await decryptResourceConnectionDetails({
encryptedConnectionDetails: resource.encryptedConnectionDetails, encryptedConnectionDetails: resource.encryptedConnectionDetails,
kmsService, kmsService,
projectId: account.projectId projectId
})) as TSqlResourceConnectionDetails; })) as TSqlResourceConnectionDetails;
const credentials = await decryptAccountCredentials({ const credentials = await decryptAccountCredentials({
encryptedCredentials: account.encryptedCredentials, encryptedCredentials: account.encryptedCredentials,
kmsService, kmsService,
projectId: account.projectId projectId
}); });
metadata = { metadata = {
username: credentials.username, username: credentials.username,
database: connectionCredentials.database, database: connectionCredentials.database,
accountName: account.name, accountName: account.name,
accountPath accountPath: folderPath
}; };
} }
break; break;
@@ -600,7 +618,7 @@ export const pamAccountServiceFactory = ({
const credentials = await decryptAccountCredentials({ const credentials = await decryptAccountCredentials({
encryptedCredentials: account.encryptedCredentials, encryptedCredentials: account.encryptedCredentials,
kmsService, kmsService,
projectId: account.projectId projectId
}); });
metadata = { metadata = {
@@ -622,7 +640,7 @@ export const pamAccountServiceFactory = ({
gatewayClientPrivateKey: gatewayConnectionDetails.gateway.clientPrivateKey, gatewayClientPrivateKey: gatewayConnectionDetails.gateway.clientPrivateKey,
gatewayServerCertificateChain: gatewayConnectionDetails.gateway.serverCertificateChain, gatewayServerCertificateChain: gatewayConnectionDetails.gateway.serverCertificateChain,
relayHost: gatewayConnectionDetails.relayHost, relayHost: gatewayConnectionDetails.relayHost,
projectId: account.projectId, projectId,
account, account,
metadata metadata
}; };
@@ -14,7 +14,8 @@ export type TUpdateAccountDTO = Partial<Omit<TCreateAccountDTO, "folderId" | "re
}; };
export type TAccessAccountDTO = { export type TAccessAccountDTO = {
accountId: string; accountPath: string;
projectId: string;
actorEmail: string; actorEmail: string;
actorIp: string; actorIp: string;
actorName: string; actorName: string;