feat: updated code by rabbit, reptile and maidul changes

This commit is contained in:
=
2025-04-21 16:27:56 +05:30
parent 649f7b560f
commit 2ee423174a
13 changed files with 83 additions and 63 deletions

View File

@@ -110,7 +110,7 @@ declare module "@fastify/request-context" {
};
};
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
projectAssumeRole?: { userId: string; actorId: string; actorType: ActorType; projectId: string };
assumedProjectRole: { requesterId: string; actorId: string; actorType: ActorType; projectId: string };
}
}

View File

@@ -6,6 +6,7 @@ import { BadRequestError } from "@app/lib/errors";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
import { requestContext } from "@fastify/request-context";
export const registerAssumePrivilegeRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -32,10 +33,10 @@ export const registerAssumePrivilegeRouter = async (server: FastifyZodProvider)
handler: async (req, res) => {
if (req.auth.authMode === AuthMode.JWT) {
const payload = await server.services.assumePrivileges.assumeProjectPrivileges({
actorType: req.body.actorType,
actorId: req.body.actorId,
targetActorType: req.body.actorType,
targetActorId: req.body.actorId,
projectId: req.params.projectId,
projectPermission: req.permission,
actorPermissionDetails: req.permission,
tokenVersionId: req.auth.tokenVersionId
});
@@ -44,20 +45,22 @@ export const registerAssumePrivilegeRouter = async (server: FastifyZodProvider)
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
secure: appCfg.HTTPS_ENABLED,
maxAge: 3600 // 1 hour in seconds
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.PROJECT_ASSUME_PRIVILEGE,
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_START,
metadata: {
projectId: req.params.projectId,
requesterEmail: req.auth.user.username,
requesterId: req.auth.user.id,
targetActorType: req.body.actorType,
targetActorId: req.body.actorId
targetActorId: req.body.actorId,
duration: "1hr"
}
}
});
@@ -87,24 +90,28 @@ export const registerAssumePrivilegeRouter = async (server: FastifyZodProvider)
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req, res) => {
if (req.auth.authMode === AuthMode.JWT) {
const assumePrivilege = requestContext.get("assumedProjectRole");
if (req.auth.authMode === AuthMode.JWT && assumePrivilege) {
const appCfg = getConfig();
void res.setCookie("infisical-project-assume-privileges", "", {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
secure: appCfg.HTTPS_ENABLED,
expires: new Date(0)
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.PROJECT_ASSUME_PRIVILEGE_EXIT,
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_END,
metadata: {
projectId: req.params.projectId,
requesterEmail: req.auth.user.username,
requesterId: req.auth.user.id
requesterId: req.auth.user.id,
targetActorId: assumePrivilege.actorId,
targetActorType: assumePrivilege.actorType
}
}
});

View File

@@ -3,7 +3,7 @@ import jwt from "jsonwebtoken";
import { ActionProjectType } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { NotFoundError } from "@app/lib/errors";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -24,23 +24,24 @@ export type TAssumePrivilegeServiceFactory = ReturnType<typeof assumePrivilegeSe
export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }: TAssumePrivilegeServiceFactoryDep) => {
const assumeProjectPrivileges = async ({
actorType,
actorId,
targetActorType,
targetActorId,
projectId,
projectPermission,
actorPermissionDetails,
tokenVersionId
}: TAssumeProjectPrivilegeDTO) => {
const project = await projectDAL.findById(projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
const { permission } = await permissionService.getProjectPermission({
actor: projectPermission.type,
actorId: projectPermission.id,
actor: actorPermissionDetails.type,
actorId: actorPermissionDetails.id,
projectId,
actorAuthMethod: projectPermission.authMethod,
actorOrgId: projectPermission.orgId,
actorAuthMethod: actorPermissionDetails.authMethod,
actorOrgId: actorPermissionDetails.orgId,
actionProjectType: ActionProjectType.Any
});
if (actorType === ActorType.USER) {
if (targetActorType === ActorType.USER) {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionMemberActions.AssumePrivileges,
ProjectPermissionSub.Member
@@ -54,11 +55,11 @@ export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }:
// check entity is part of project
await permissionService.getProjectPermission({
actor: actorType,
actorId,
actor: targetActorType,
actorId: targetActorId,
projectId,
actorAuthMethod: projectPermission.authMethod,
actorOrgId: projectPermission.orgId,
actorAuthMethod: actorPermissionDetails.authMethod,
actorOrgId: actorPermissionDetails.orgId,
actionProjectType: ActionProjectType.Any
});
@@ -66,16 +67,16 @@ export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }:
const assumePrivilegesToken = jwt.sign(
{
tokenVersionId,
actorType,
actorId,
actorType: targetActorType,
actorId: targetActorId,
projectId,
userId: projectPermission.id
requesterId: actorPermissionDetails.id
},
appCfg.AUTH_SECRET,
{ expiresIn: "1hr" }
);
return { actorType, actorId, projectId, assumePrivilegesToken };
return { actorType: targetActorType, actorId: targetActorId, projectId, assumePrivilegesToken };
};
const verifyAssumePrivilegeToken = (token: string, tokenVersionId: string) => {
@@ -83,11 +84,13 @@ export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }:
const decodedToken = jwt.verify(token, appCfg.AUTH_SECRET) as {
tokenVersionId: string;
projectId: string;
userId: string;
requesterId: string;
actorType: ActorType;
actorId: string;
};
if (decodedToken.tokenVersionId !== tokenVersionId) return;
if (decodedToken.tokenVersionId !== tokenVersionId) {
throw new ForbiddenRequestError({ message: "Invalid token version" });
}
return decodedToken;
};

View File

@@ -2,9 +2,9 @@ import { OrgServiceActor } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
export type TAssumeProjectPrivilegeDTO = {
actorType: ActorType.USER | ActorType.IDENTITY;
actorId: string;
targetActorType: ActorType.USER | ActorType.IDENTITY;
targetActorId: string;
projectId: string;
tokenVersionId: string;
projectPermission: OrgServiceActor;
actorPermissionDetails: OrgServiceActor;
};

View File

@@ -318,8 +318,8 @@ export enum EventType {
SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets",
PROJECT_ACCESS_REQUEST = "project-access-request",
PROJECT_ASSUME_PRIVILEGE = "project-assume-privileges",
PROJECT_ASSUME_PRIVILEGE_EXIT = "project-assume-privileges-exit"
PROJECT_ASSUME_PRIVILEGE_SESSION_START = "project-assume-privileges-session-start",
PROJECT_ASSUME_PRIVILEGE_SESSION_END = "project-assume-privileges-session-end"
}
export const filterableSecretEvents: EventType[] = [
@@ -2428,22 +2428,25 @@ interface ProjectAccessRequestEvent {
}
interface ProjectAssumePrivilegesEvent {
type: EventType.PROJECT_ASSUME_PRIVILEGE;
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_START;
metadata: {
projectId: string;
requesterId: string;
requesterEmail: string;
targetActorType: ActorType;
targetActorId: string;
duration: string;
};
}
interface ProjectAssumePrivilegesExitEvent {
type: EventType.PROJECT_ASSUME_PRIVILEGE_EXIT;
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_END;
metadata: {
projectId: string;
requesterId: string;
requesterEmail: string;
targetActorType: ActorType;
targetActorId: string;
};
}

View File

@@ -560,15 +560,15 @@ export const permissionServiceFactory = ({
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
let actor = inputActor;
let actorId = inputActorId;
const projectAssumeRole = requestContext.get("projectAssumeRole");
const assumedProjectRole = requestContext.get("assumedProjectRole");
if (
projectAssumeRole &&
assumedProjectRole &&
actor === ActorType.USER &&
actorId === projectAssumeRole.userId &&
projectId === projectAssumeRole.projectId
actorId === assumedProjectRole.requesterId &&
projectId === assumedProjectRole.projectId
) {
actor = projectAssumeRole.actorType;
actorId = projectAssumeRole.actorId;
actor = assumedProjectRole.actorType;
actorId = assumedProjectRole.actorId;
}
switch (actor) {

View File

@@ -4,16 +4,21 @@ import fp from "fastify-plugin";
import { AuthMode } from "@app/services/auth/auth-type";
export const injectAssumePrivilege = fp(async (server: FastifyZodProvider) => {
server.addHook("onRequest", async (req) => {
server.addHook("onRequest", async (req, res) => {
const assumeRoleCookie = req.cookies["infisical-project-assume-privileges"];
if (req?.auth?.authMode === AuthMode.JWT && assumeRoleCookie) {
const decodedToken = server.services.assumePrivileges.verifyAssumePrivilegeToken(
assumeRoleCookie,
req.auth.tokenVersionId
);
if (decodedToken) {
requestContext.set("projectAssumeRole", decodedToken);
try {
if (req?.auth?.authMode === AuthMode.JWT && assumeRoleCookie) {
const decodedToken = server.services.assumePrivileges.verifyAssumePrivilegeToken(
assumeRoleCookie,
req.auth.tokenVersionId
);
if (decodedToken) {
requestContext.set("assumedProjectRole", decodedToken);
}
}
} catch (error) {
req.log.error({ error }, "Failed to verify assume privilege token");
void res.clearCookie("infisical-project-assume-privileges");
}
});
});

View File

@@ -37,7 +37,8 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
secure: appCfg.HTTPS_ENABLED,
maxAge: 0
});
return { message: "Successfully logged out" };

View File

@@ -81,7 +81,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: cfg.HTTPS_ENABLED
secure: cfg.HTTPS_ENABLED,
maxAge: 0
});
return { token: tokens.access, isMfaEnabled: false };
@@ -142,7 +143,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: appCfg.HTTPS_ENABLED
secure: appCfg.HTTPS_ENABLED,
maxAge: 0
});
return {

View File

@@ -238,21 +238,23 @@ export const projectRoleServiceFactory = ({
// just to satisfy ts
if (!("roles" in membership)) throw new BadRequestError({ message: "Service token not allowed" });
const projectAssumeRole = requestContext.get("projectAssumeRole");
const isImpersonating = projectAssumeRole?.projectId === projectId;
const assumedProjectRole = requestContext.get("assumedProjectRole");
const isImpersonating = assumedProjectRole?.projectId === projectId;
const impersonation = isImpersonating
? {
actorId: projectAssumeRole?.actorId,
actorType: projectAssumeRole?.actorType,
actorId: assumedProjectRole?.actorId,
actorType: assumedProjectRole?.actorType,
actorName: "",
actorEmail: ""
}
: undefined;
if (impersonation?.actorType === ActorType.IDENTITY) {
const identityDetails = await identityDAL.findById(impersonation.actorId);
if (!identityDetails) throw new NotFoundError({ message: `Identity with ID ${impersonation.actorId} not found` });
impersonation.actorName = identityDetails.name;
} else if (impersonation?.actorType === ActorType.USER) {
const userDetails = await userDAL.findById(impersonation?.actorId);
if (!userDetails) throw new NotFoundError({ message: `User with ID ${impersonation.actorId} not found` });
impersonation.actorName = `${userDetails?.firstName} ${userDetails?.lastName || ""}`;
impersonation.actorEmail = userDetails?.email || "";
}

View File

@@ -45,8 +45,6 @@ export const ConfirmActionModal = ({
setIsLoading.on();
try {
await onConfirmed();
} catch {
setIsLoading.off();
} finally {
setIsLoading.off();
}
@@ -77,7 +75,7 @@ export const ConfirmActionModal = ({
<Button variant="plain" colorSchema="secondary" onClick={onClose}>
Cancel
</Button>
</ModalClose>{" "}
</ModalClose>
</div>
}
onClose={onClose}

View File

@@ -49,7 +49,6 @@ const Page = () => {
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteIdentity",
"upgradePlan",
"assumePrivileges"
] as const);
const assumePrivileges = useAssumeProjectPrivileges();

View File

@@ -136,7 +136,7 @@ export const Page = () => {
variant="outline_bg"
size="xs"
isDisabled={!isAllowed}
isLoading={isRemovingUserFromWorkspace}
isLoading={assumePrivileges.isPending}
onClick={() =>
handlePopUpOpen("assumePrivileges", { userId: membershipDetails?.user?.id })
}