mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-09 15:38:03 -05:00
Merge pull request #4577 from Infisical/feat/ENG-3724
Add project permission cache
This commit is contained in:
@@ -35,7 +35,7 @@ import { ApprovalStatus, TAccessApprovalRequestServiceFactory } from "./access-a
|
||||
|
||||
type TSecretApprovalRequestServiceFactoryDep = {
|
||||
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "create" | "findById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
|
||||
accessApprovalPolicyApproverDAL: Pick<TAccessApprovalPolicyApproverDALFactory, "find">;
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||
projectDAL: Pick<
|
||||
@@ -758,6 +758,8 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
{ privilegeId: privilegeIdToSet, status: ApprovalStatus.APPROVED },
|
||||
tx
|
||||
);
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(accessApprovalRequest.projectId, tx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,10 @@ type TGroupServiceFactoryDep = {
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
||||
permissionService: Pick<
|
||||
TPermissionServiceFactory,
|
||||
"getOrgPermission" | "getOrgPermissionByRole" | "invalidateProjectPermissionCache"
|
||||
>;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne">;
|
||||
};
|
||||
@@ -225,6 +228,15 @@ export const groupServiceFactory = ({
|
||||
return updated;
|
||||
});
|
||||
|
||||
if (role) {
|
||||
const groupProjects = await groupProjectDAL.find({ groupId: group.id });
|
||||
await Promise.allSettled([
|
||||
...groupProjects.map((groupProject) =>
|
||||
permissionService.invalidateProjectPermissionCache(groupProject.projectId)
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
return updatedGroup;
|
||||
};
|
||||
|
||||
@@ -247,11 +259,17 @@ export const groupServiceFactory = ({
|
||||
message: "Failed to delete group due to plan restriction. Upgrade plan to delete group."
|
||||
});
|
||||
|
||||
const groupProjects = await groupProjectDAL.find({ groupId: id });
|
||||
|
||||
const [group] = await groupDAL.delete({
|
||||
id,
|
||||
orgId: actorOrgId
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
...groupProjects.map((groupProject) => permissionService.invalidateProjectPermissionCache(groupProject.projectId))
|
||||
]);
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
@@ -398,6 +416,11 @@ export const groupServiceFactory = ({
|
||||
projectBotDAL
|
||||
});
|
||||
|
||||
const groupProjects = await groupProjectDAL.find({ groupId: group.id });
|
||||
await Promise.allSettled([
|
||||
...groupProjects.map((groupProject) => permissionService.invalidateProjectPermissionCache(groupProject.projectId))
|
||||
]);
|
||||
|
||||
return users[0];
|
||||
};
|
||||
|
||||
@@ -479,6 +502,11 @@ export const groupServiceFactory = ({
|
||||
projectKeyDAL
|
||||
});
|
||||
|
||||
const groupProjects = await groupProjectDAL.find({ groupId: group.id });
|
||||
await Promise.allSettled([
|
||||
...groupProjects.map((groupProject) => permissionService.invalidateProjectPermissionCache(groupProject.projectId))
|
||||
]);
|
||||
|
||||
return users[0];
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ type TIdentityProjectAdditionalPrivilegeV2ServiceFactoryDep = {
|
||||
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeV2DALFactory;
|
||||
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
|
||||
};
|
||||
|
||||
export type TIdentityProjectAdditionalPrivilegeV2ServiceFactory = ReturnType<
|
||||
@@ -115,6 +115,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
permissions: packedPermission
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -132,6 +134,9 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -224,6 +229,9 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -239,6 +247,9 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
temporaryRange: null,
|
||||
temporaryMode: null
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -294,6 +305,9 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
});
|
||||
|
||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...deletedPrivilege,
|
||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||
|
||||
@@ -31,7 +31,7 @@ type TIdentityProjectAdditionalPrivilegeServiceFactoryDep = {
|
||||
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeDALFactory;
|
||||
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
|
||||
};
|
||||
|
||||
export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
|
||||
@@ -129,6 +129,9 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
slug,
|
||||
permissions: packedPermission
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -146,6 +149,9 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -250,6 +256,9 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -265,6 +274,9 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryRange: null,
|
||||
temporaryMode: null
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -338,9 +350,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
}
|
||||
|
||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...deletedPrivilege,
|
||||
|
||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { MongoQuery } from "@ucast/mongo2js";
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
@@ -283,4 +284,5 @@ export type TPermissionServiceFactory = {
|
||||
projectId: string;
|
||||
checkPermissions: ProjectPermissionSet;
|
||||
}) => Promise<boolean>;
|
||||
invalidateProjectPermissionCache: (projectId: string, tx?: Knex) => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { PackRule, unpackRules } from "@casl/ability/extra";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
import { MongoQuery } from "@ucast/mongo2js";
|
||||
import handlebars from "handlebars";
|
||||
import { Knex } from "knex";
|
||||
|
||||
import {
|
||||
ActionProjectType,
|
||||
@@ -20,9 +21,11 @@ import {
|
||||
projectViewerPermission,
|
||||
sshHostBootstrapPermissions
|
||||
} from "@app/ee/services/permission/default-roles";
|
||||
import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { conditionsMatcher } from "@app/lib/casl";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { objectify } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
@@ -49,6 +52,7 @@ type TPermissionServiceFactoryDep = {
|
||||
serviceTokenDAL: Pick<TServiceTokenDALFactory, "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
permissionDAL: TPermissionDALFactory;
|
||||
keyStore: TKeyStoreFactory;
|
||||
};
|
||||
|
||||
export const permissionServiceFactory = ({
|
||||
@@ -56,7 +60,8 @@ export const permissionServiceFactory = ({
|
||||
orgRoleDAL,
|
||||
projectRoleDAL,
|
||||
serviceTokenDAL,
|
||||
projectDAL
|
||||
projectDAL,
|
||||
keyStore
|
||||
}: TPermissionServiceFactoryDep): TPermissionServiceFactory => {
|
||||
const buildOrgPermission = (orgUserRoles: TBuildOrgPermissionDTO) => {
|
||||
const rules = orgUserRoles
|
||||
@@ -83,6 +88,68 @@ export const permissionServiceFactory = ({
|
||||
});
|
||||
};
|
||||
|
||||
const invalidateProjectPermissionCache = async (projectId: string, tx?: Knex) => {
|
||||
const projectPermissionDalVersionKey = KeyStorePrefixes.ProjectPermissionDalVersion(projectId);
|
||||
await keyStore.pgIncrementBy(projectPermissionDalVersionKey, {
|
||||
incr: 1,
|
||||
tx,
|
||||
expiry: KeyStoreTtls.ProjectPermissionDalVersionTtl
|
||||
});
|
||||
};
|
||||
|
||||
const calculateProjectPermissionTtl = (membership: unknown): number => {
|
||||
const now = new Date();
|
||||
let minTtl = KeyStoreTtls.ProjectPermissionCacheInSeconds;
|
||||
|
||||
const getMinEndTime = (items: Array<{ temporaryAccessEndTime?: Date | null; isTemporary?: boolean }>) => {
|
||||
return items
|
||||
.filter((item) => item.isTemporary && item.temporaryAccessEndTime)
|
||||
.map((item) => item.temporaryAccessEndTime!)
|
||||
.filter((endTime) => endTime > now)
|
||||
.reduce((min, endTime) => (!min || endTime < min ? endTime : min), null as Date | null);
|
||||
};
|
||||
|
||||
const roleTimes: Date[] = [];
|
||||
const additionalPrivilegeTimes: Date[] = [];
|
||||
|
||||
if (
|
||||
membership &&
|
||||
typeof membership === "object" &&
|
||||
"roles" in membership &&
|
||||
Array.isArray((membership as Record<string, unknown>).roles)
|
||||
) {
|
||||
const roles = (membership as Record<string, unknown>).roles as Array<{
|
||||
temporaryAccessEndTime?: Date | null;
|
||||
isTemporary?: boolean;
|
||||
}>;
|
||||
const minRoleEndTime = getMinEndTime(roles);
|
||||
if (minRoleEndTime) roleTimes.push(minRoleEndTime);
|
||||
}
|
||||
|
||||
if (
|
||||
membership &&
|
||||
typeof membership === "object" &&
|
||||
"additionalPrivileges" in membership &&
|
||||
Array.isArray((membership as Record<string, unknown>).additionalPrivileges)
|
||||
) {
|
||||
const additionalPrivileges = (membership as Record<string, unknown>).additionalPrivileges as Array<{
|
||||
temporaryAccessEndTime?: Date | null;
|
||||
isTemporary?: boolean;
|
||||
}>;
|
||||
const minAdditionalEndTime = getMinEndTime(additionalPrivileges);
|
||||
if (minAdditionalEndTime) additionalPrivilegeTimes.push(minAdditionalEndTime);
|
||||
}
|
||||
|
||||
const allEndTimes = [...roleTimes, ...additionalPrivilegeTimes];
|
||||
if (allEndTimes.length > 0) {
|
||||
const nearestEndTime = allEndTimes.reduce((min, endTime) => (!min || endTime < min ? endTime : min));
|
||||
const timeUntilExpiry = Math.floor((nearestEndTime.getTime() - now.getTime()) / 1000);
|
||||
minTtl = Math.min(minTtl, Math.max(1, timeUntilExpiry));
|
||||
}
|
||||
|
||||
return minTtl;
|
||||
};
|
||||
|
||||
const buildProjectPermissionRules = (projectUserRoles: TBuildProjectPermissionDTO) => {
|
||||
const rules = projectUserRoles
|
||||
.map(({ role, permissions }) => {
|
||||
@@ -577,35 +644,94 @@ export const permissionServiceFactory = ({
|
||||
actorId = assumedPrivilegeDetailsCtx.actorId;
|
||||
}
|
||||
|
||||
if (actor === ActorType.SERVICE) {
|
||||
return getServiceTokenProjectPermission({
|
||||
serviceTokenId: actorId,
|
||||
projectId,
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
}
|
||||
|
||||
const cachedProjectPermissionVersion = await keyStore.pgGetIntItem(
|
||||
KeyStorePrefixes.ProjectPermissionDalVersion(projectId)
|
||||
);
|
||||
const projectPermissionVersion = Number(cachedProjectPermissionVersion || 0);
|
||||
const cacheKey = KeyStorePrefixes.ProjectPermission(
|
||||
projectId,
|
||||
projectPermissionVersion,
|
||||
actor,
|
||||
actorId,
|
||||
actionProjectType || ActionProjectType.Any
|
||||
);
|
||||
|
||||
try {
|
||||
const cachedData = await keyStore.getItem(cacheKey);
|
||||
if (cachedData) {
|
||||
const parsed = JSON.parse(cachedData) as {
|
||||
rules: RawRuleOf<MongoAbility<ProjectPermissionSet>>[];
|
||||
membership: {
|
||||
roles?: Array<{ role: string; customRoleSlug?: string }>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
const permission = createMongoAbility<ProjectPermissionSet>(parsed.rules, {
|
||||
conditionsMatcher
|
||||
});
|
||||
|
||||
return {
|
||||
permission,
|
||||
membership: parsed.membership,
|
||||
hasRole: (role: string) =>
|
||||
parsed.membership.roles?.findIndex(
|
||||
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||
) !== -1
|
||||
} as TProjectPermissionRT<T>;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to get project permission");
|
||||
}
|
||||
|
||||
let result: TProjectPermissionRT<T>;
|
||||
|
||||
switch (actor) {
|
||||
case ActorType.USER:
|
||||
return getUserProjectPermission({
|
||||
result = (await getUserProjectPermission({
|
||||
userId: actorId,
|
||||
projectId,
|
||||
authMethod: actorAuthMethod,
|
||||
userOrgId: actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
case ActorType.SERVICE:
|
||||
return getServiceTokenProjectPermission({
|
||||
serviceTokenId: actorId,
|
||||
projectId,
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
})) as TProjectPermissionRT<T>;
|
||||
break;
|
||||
case ActorType.IDENTITY:
|
||||
return getIdentityProjectPermission({
|
||||
result = (await getIdentityProjectPermission({
|
||||
identityId: actorId,
|
||||
projectId,
|
||||
identityOrgId: actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
})) as TProjectPermissionRT<T>;
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestError({
|
||||
message: "Invalid actor provided",
|
||||
name: "Get project permission"
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const cacheData = {
|
||||
rules: result.permission.rules,
|
||||
membership: result.membership
|
||||
};
|
||||
|
||||
const ttl = calculateProjectPermissionTtl(result.membership);
|
||||
await keyStore.setItemWithExpiry(cacheKey, ttl, JSON.stringify(cacheData));
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to cache project permission");
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const getProjectPermissionByRole: TPermissionServiceFactory["getProjectPermissionByRole"] = async (
|
||||
@@ -668,6 +794,7 @@ export const permissionServiceFactory = ({
|
||||
getProjectPermissionByRole,
|
||||
buildOrgPermission,
|
||||
buildProjectPermissionRules,
|
||||
checkGroupProjectPermission
|
||||
checkGroupProjectPermission,
|
||||
invalidateProjectPermissionCache
|
||||
};
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
||||
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
|
||||
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update">;
|
||||
};
|
||||
|
||||
@@ -115,6 +115,9 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
slug,
|
||||
permissions: packedPermission
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -133,6 +136,9 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -230,6 +236,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -245,6 +253,9 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryRange: null,
|
||||
temporaryMode: null
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -291,6 +302,9 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
}
|
||||
);
|
||||
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
||||
|
||||
return {
|
||||
...deletedPrivilege,
|
||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||
|
||||
@@ -63,13 +63,28 @@ export const KeyStorePrefixes = {
|
||||
ActiveSSEConnectionsSet: (projectId: string, identityId: string) =>
|
||||
`sse-connections:${projectId}:${identityId}` as const,
|
||||
ActiveSSEConnections: (projectId: string, identityId: string, connectionId: string) =>
|
||||
`sse-connections:${projectId}:${identityId}:${connectionId}` as const
|
||||
`sse-connections:${projectId}:${identityId}:${connectionId}` as const,
|
||||
|
||||
ProjectPermission: (
|
||||
projectId: string,
|
||||
version: number,
|
||||
actorType: string,
|
||||
actorId: string,
|
||||
actionProjectType: string
|
||||
) => `project-permission:${projectId}:${version}:${actorType}:${actorId}:${actionProjectType}` as const,
|
||||
ProjectPermissionDalVersion: (projectId: string) => `project-permission:${projectId}:dal-version` as const,
|
||||
UserProjectPermissionPattern: (userId: string) => `project-permission:*:*:USER:${userId}:*` as const,
|
||||
IdentityProjectPermissionPattern: (identityId: string) => `project-permission:*:*:IDENTITY:${identityId}:*` as const,
|
||||
GroupMemberProjectPermissionPattern: (projectId: string, groupId: string) =>
|
||||
`group-member-project-permission:${projectId}:${groupId}:*` as const
|
||||
};
|
||||
|
||||
export const KeyStoreTtls = {
|
||||
SetSyncSecretIntegrationLastRunTimestampInSeconds: 60,
|
||||
SetSecretSyncLastRunTimestampInSeconds: 60,
|
||||
AccessTokenStatusUpdateInSeconds: 120
|
||||
AccessTokenStatusUpdateInSeconds: 120,
|
||||
ProjectPermissionCacheInSeconds: 300, // 5 minutes
|
||||
ProjectPermissionDalVersionTtl: "15m" // Project permission DAL version TTL
|
||||
};
|
||||
|
||||
type TDeleteItems = {
|
||||
|
||||
@@ -531,7 +531,8 @@ export const registerRoutes = async (
|
||||
orgRoleDAL,
|
||||
projectRoleDAL,
|
||||
serviceTokenDAL,
|
||||
projectDAL
|
||||
projectDAL,
|
||||
keyStore
|
||||
});
|
||||
const assumePrivilegeService = assumePrivilegeServiceFactory({
|
||||
projectDAL,
|
||||
|
||||
@@ -37,13 +37,16 @@ type TGroupProjectServiceFactoryDep = {
|
||||
TGroupProjectMembershipRoleDALFactory,
|
||||
"create" | "transaction" | "insertMany" | "delete"
|
||||
>;
|
||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "findGroupMembersNotInProject">;
|
||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "findGroupMembersNotInProject" | "find">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findOne" | "findProjectGhostUser" | "findById">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany" | "transaction">;
|
||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
||||
projectBotDAL: TProjectBotDALFactory;
|
||||
groupDAL: Pick<TGroupDALFactory, "findOne" | "findAllGroupPossibleMembers">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRole">;
|
||||
permissionService: Pick<
|
||||
TPermissionServiceFactory,
|
||||
"getProjectPermission" | "getProjectPermissionByRole" | "invalidateProjectPermissionCache"
|
||||
>;
|
||||
};
|
||||
|
||||
export type TGroupProjectServiceFactory = ReturnType<typeof groupProjectServiceFactory>;
|
||||
@@ -263,6 +266,8 @@ export const groupProjectServiceFactory = ({
|
||||
return groupProjectMembership;
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
||||
|
||||
return projectGroup;
|
||||
};
|
||||
|
||||
@@ -372,6 +377,8 @@ export const groupProjectServiceFactory = ({
|
||||
return groupProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
||||
|
||||
return updatedRoles;
|
||||
};
|
||||
|
||||
@@ -404,14 +411,18 @@ export const groupProjectServiceFactory = ({
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Delete, ProjectPermissionSub.Groups);
|
||||
|
||||
const deletedProjectGroup = await groupProjectDAL.transaction(async (tx) => {
|
||||
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group.id, project.id, tx);
|
||||
const groupMembersNotInProject = await userGroupMembershipDAL.findGroupMembersNotInProject(
|
||||
group.id,
|
||||
project.id,
|
||||
tx
|
||||
);
|
||||
|
||||
if (groupMembers.length) {
|
||||
if (groupMembersNotInProject.length) {
|
||||
await projectKeyDAL.delete(
|
||||
{
|
||||
projectId: project.id,
|
||||
$in: {
|
||||
receiverId: groupMembers.map(({ user: { id } }) => id)
|
||||
receiverId: groupMembersNotInProject.map(({ user: { id } }) => id)
|
||||
}
|
||||
},
|
||||
tx
|
||||
@@ -422,6 +433,8 @@ export const groupProjectServiceFactory = ({
|
||||
return projectGroup;
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
||||
|
||||
return deletedProjectGroup;
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,10 @@ type TIdentityProjectServiceFactoryDep = {
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
||||
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRole">;
|
||||
permissionService: Pick<
|
||||
TPermissionServiceFactory,
|
||||
"getProjectPermission" | "getProjectPermissionByRole" | "invalidateProjectPermissionCache"
|
||||
>;
|
||||
};
|
||||
|
||||
export type TIdentityProjectServiceFactory = ReturnType<typeof identityProjectServiceFactory>;
|
||||
@@ -165,6 +168,9 @@ export const identityProjectServiceFactory = ({
|
||||
const identityRoles = await identityProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
||||
return { ...identityProjectMembership, roles: identityRoles };
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
||||
|
||||
return projectIdentity;
|
||||
};
|
||||
|
||||
@@ -272,6 +278,8 @@ export const identityProjectServiceFactory = ({
|
||||
return identityProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
||||
|
||||
return updatedRoles;
|
||||
};
|
||||
|
||||
@@ -302,6 +310,9 @@ export const identityProjectServiceFactory = ({
|
||||
);
|
||||
|
||||
const [deletedIdentity] = await identityProjectDAL.delete({ identityId, projectId });
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
||||
|
||||
return deletedIdentity;
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,10 @@ import {
|
||||
import { TProjectUserMembershipRoleDALFactory } from "./project-user-membership-role-dal";
|
||||
|
||||
type TProjectMembershipServiceFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRole">;
|
||||
permissionService: Pick<
|
||||
TPermissionServiceFactory,
|
||||
"getProjectPermission" | "getProjectPermissionByRole" | "invalidateProjectPermissionCache"
|
||||
>;
|
||||
smtpService: TSmtpService;
|
||||
projectBotDAL: TProjectBotDALFactory;
|
||||
projectMembershipDAL: TProjectMembershipDALFactory;
|
||||
@@ -239,6 +242,8 @@ export const projectMembershipServiceFactory = ({
|
||||
);
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
||||
|
||||
if (sendEmails) {
|
||||
await notificationService.createUserNotifications(
|
||||
orgMembers.map((member) => ({
|
||||
@@ -371,6 +376,8 @@ export const projectMembershipServiceFactory = ({
|
||||
return projectUserMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
||||
|
||||
return updatedRoles;
|
||||
};
|
||||
|
||||
@@ -414,6 +421,9 @@ export const projectMembershipServiceFactory = ({
|
||||
);
|
||||
return deletedMembership;
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
||||
|
||||
return membership;
|
||||
};
|
||||
|
||||
@@ -515,6 +525,9 @@ export const projectMembershipServiceFactory = ({
|
||||
|
||||
return deletedMemberships;
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
||||
|
||||
return memberships;
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,10 @@ type TProjectRoleServiceFactoryDep = {
|
||||
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
||||
userDAL: Pick<TUserDALFactory, "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findProjectById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">;
|
||||
permissionService: Pick<
|
||||
TPermissionServiceFactory,
|
||||
"getProjectPermission" | "getUserProjectPermission" | "invalidateProjectPermissionCache"
|
||||
>;
|
||||
identityProjectMembershipRoleDAL: TIdentityProjectMembershipRoleDALFactory;
|
||||
projectUserMembershipRoleDAL: TProjectUserMembershipRoleDALFactory;
|
||||
};
|
||||
@@ -162,6 +165,8 @@ export const projectRoleServiceFactory = ({
|
||||
});
|
||||
if (!updatedRole) throw new NotFoundError({ message: "Project role not found", name: "Update role" });
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectRole.projectId);
|
||||
|
||||
return { ...updatedRole, permissions: unpackPermissions(updatedRole.permissions) };
|
||||
};
|
||||
|
||||
@@ -197,6 +202,8 @@ export const projectRoleServiceFactory = ({
|
||||
const deletedRole = await projectRoleDAL.deleteById(roleId);
|
||||
if (!deletedRole) throw new NotFoundError({ message: "Project role not found", name: "Delete role" });
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectRole.projectId);
|
||||
|
||||
return { ...deletedRole, permissions: unpackPermissions(deletedRole.permissions) };
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user