mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: updated code by rabbit, reptile and maidul changes
This commit is contained in:
2
backend/src/@types/fastify.d.ts
vendored
2
backend/src/@types/fastify.d.ts
vendored
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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" };
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 || "";
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -49,7 +49,6 @@ const Page = () => {
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"deleteIdentity",
|
||||
"upgradePlan",
|
||||
"assumePrivileges"
|
||||
] as const);
|
||||
const assumePrivileges = useAssumeProjectPrivileges();
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user