mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Add/remove bulk users to projects upon add/remove users to/from groups
This commit is contained in:
@@ -155,6 +155,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
|
||||
return { data: { permissions, membership } };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
|
||||
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
||||
import { OrgMembershipRole, SecretKeyEncoding, TOrgRoles } from "@app/db/schemas";
|
||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||
import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { TGroupProjectDALFactory } from "../../../services/group-project/group-project-dal";
|
||||
import { TOrgDALFactory } from "../../../services/org/org-dal";
|
||||
import { TProjectDALFactory } from "../../../services/project/project-dal";
|
||||
import { TProjectBotDALFactory } from "../../../services/project-bot/project-bot-dal";
|
||||
import { TProjectKeyDALFactory } from "../../../services/project-key/project-key-dal";
|
||||
import { TUserDALFactory } from "../../../services/user/user-dal";
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
@@ -23,10 +28,17 @@ import {
|
||||
import { TUserGroupMembershipDALFactory } from "./user-group-membership-dal";
|
||||
|
||||
type TGroupServiceFactoryDep = {
|
||||
userDAL: Pick<TUserDALFactory, "findOne">;
|
||||
userDAL: Pick<TUserDALFactory, "findOne" | "findUserEncKeyByUsername">;
|
||||
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "update" | "delete" | "findAllGroupMembers">;
|
||||
groupProjectDAL: TGroupProjectDALFactory;
|
||||
orgDAL: Pick<TOrgDALFactory, "findMembership">;
|
||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "findOne" | "create" | "delete">;
|
||||
userGroupMembershipDAL: Pick<
|
||||
TUserGroupMembershipDALFactory,
|
||||
"findOne" | "create" | "delete" | "filterProjectsByUserMembership"
|
||||
>;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "create" | "delete" | "findLatestProjectKey">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
@@ -36,8 +48,12 @@ export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
|
||||
export const groupServiceFactory = ({
|
||||
userDAL,
|
||||
groupDAL,
|
||||
groupProjectDAL,
|
||||
orgDAL,
|
||||
userGroupMembershipDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
projectKeyDAL,
|
||||
permissionService,
|
||||
licenseService
|
||||
}: TGroupServiceFactoryDep) => {
|
||||
@@ -211,7 +227,7 @@ export const groupServiceFactory = ({
|
||||
throw new ForbiddenRequestError({ message: "Failed to add user to more privileged group" });
|
||||
|
||||
// get user with username
|
||||
const user = await userDAL.findOne({
|
||||
const user = await userDAL.findUserEncKeyByUsername({
|
||||
username
|
||||
});
|
||||
|
||||
@@ -223,7 +239,7 @@ export const groupServiceFactory = ({
|
||||
// check if user group membership already exists
|
||||
const existingUserGroupMembership = await userGroupMembershipDAL.findOne({
|
||||
groupId: group.id,
|
||||
userId: user.id
|
||||
userId: user.userId
|
||||
});
|
||||
|
||||
if (existingUserGroupMembership)
|
||||
@@ -233,7 +249,7 @@ export const groupServiceFactory = ({
|
||||
|
||||
// check if user is even part of the organization
|
||||
const existingUserOrgMembership = await orgDAL.findMembership({
|
||||
userId: user.id,
|
||||
userId: user.userId,
|
||||
orgId
|
||||
});
|
||||
|
||||
@@ -243,10 +259,77 @@ export const groupServiceFactory = ({
|
||||
});
|
||||
|
||||
await userGroupMembershipDAL.create({
|
||||
userId: user.id,
|
||||
userId: user.userId,
|
||||
groupId: group.id
|
||||
});
|
||||
|
||||
// check which projects the group is part of
|
||||
const projectIds = (
|
||||
await groupProjectDAL.find({
|
||||
groupId: group.id
|
||||
})
|
||||
).map((gp) => gp.projectId);
|
||||
|
||||
const keys = await projectKeyDAL.find({
|
||||
receiverId: user.userId,
|
||||
$in: {
|
||||
projectId: projectIds
|
||||
}
|
||||
});
|
||||
|
||||
const keysSet = new Set(keys.map((k) => k.projectId));
|
||||
const projectsToAddKeyFor = projectIds.filter((p) => !keysSet.has(p));
|
||||
|
||||
for await (const projectId of projectsToAddKeyFor) {
|
||||
const ghostUser = await projectDAL.findProjectGhostUser(projectId);
|
||||
|
||||
if (!ghostUser) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find sudo user"
|
||||
});
|
||||
}
|
||||
|
||||
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId);
|
||||
|
||||
if (!ghostUserLatestKey) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find sudo user latest key"
|
||||
});
|
||||
}
|
||||
|
||||
const bot = await projectBotDAL.findOne({ projectId });
|
||||
|
||||
if (!bot) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find bot"
|
||||
});
|
||||
}
|
||||
|
||||
const botPrivateKey = infisicalSymmetricDecrypt({
|
||||
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
ciphertext: bot.encryptedPrivateKey
|
||||
});
|
||||
|
||||
const plaintextProjectKey = decryptAsymmetric({
|
||||
ciphertext: ghostUserLatestKey.encryptedKey,
|
||||
nonce: ghostUserLatestKey.nonce,
|
||||
publicKey: ghostUserLatestKey.sender.publicKey,
|
||||
privateKey: botPrivateKey
|
||||
});
|
||||
|
||||
const { ciphertext: encryptedKey, nonce } = encryptAsymmetric(plaintextProjectKey, user.publicKey, botPrivateKey);
|
||||
|
||||
await projectKeyDAL.create({
|
||||
encryptedKey,
|
||||
nonce,
|
||||
senderId: ghostUser.id,
|
||||
receiverId: user.userId,
|
||||
projectId
|
||||
});
|
||||
}
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
@@ -300,6 +383,25 @@ export const groupServiceFactory = ({
|
||||
message: `User ${username} is not part of the group ${groupSlug}`
|
||||
});
|
||||
|
||||
const projectIds = (
|
||||
await groupProjectDAL.find({
|
||||
groupId: group.id
|
||||
})
|
||||
).map((gp) => gp.projectId);
|
||||
|
||||
const t = await userGroupMembershipDAL.filterProjectsByUserMembership(user.id, group.id, projectIds);
|
||||
|
||||
const projectsToDeleteKeyFor = projectIds.filter((p) => !t.has(p));
|
||||
|
||||
if (projectsToDeleteKeyFor.length) {
|
||||
await projectKeyDAL.delete({
|
||||
receiverId: user.id,
|
||||
$in: {
|
||||
projectId: projectsToDeleteKeyFor
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await userGroupMembershipDAL.delete({
|
||||
groupId: group.id,
|
||||
userId: user.id
|
||||
|
||||
@@ -8,42 +8,49 @@ export type TUserGroupMembershipDALFactory = ReturnType<typeof userGroupMembersh
|
||||
export const userGroupMembershipDALFactory = (db: TDbClient) => {
|
||||
const userGroupMembershipOrm = ormify(db, TableName.UserGroupMembership);
|
||||
|
||||
// special query
|
||||
const findGroupMembersInProject = async (groupId: string, projectId: string) => {
|
||||
try {
|
||||
const members = await db(TableName.UserGroupMembership)
|
||||
.where(`${TableName.UserGroupMembership}.groupId`, groupId)
|
||||
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||
.join(TableName.ProjectMembership, function () {
|
||||
this.on(`${TableName.Users}.id`, "=", `${TableName.ProjectMembership}.userId`).andOn(
|
||||
`${TableName.ProjectMembership}.projectId`,
|
||||
"=",
|
||||
db.raw("?", [projectId])
|
||||
);
|
||||
})
|
||||
.leftJoin<TUserEncryptionKeys>(
|
||||
TableName.UserEncryptionKey,
|
||||
`${TableName.UserEncryptionKey}.userId`,
|
||||
`${TableName.Users}.id`
|
||||
)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("groupId").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("email").withSchema(TableName.Users),
|
||||
db.ref("username").withSchema(TableName.Users),
|
||||
db.ref("firstName").withSchema(TableName.Users),
|
||||
db.ref("lastName").withSchema(TableName.Users),
|
||||
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||
db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
|
||||
)
|
||||
.where({ isGhost: false }); // MAKE SURE USER IS NOT A GHOST USER
|
||||
/**
|
||||
* Returns a sub-set of projectIds fed into this function corresponding to projects where either:
|
||||
* - The user is a direct member of the project.
|
||||
* - The user is a member of a group that is a member of the project, excluding projects that they are part of
|
||||
* through the group with id [groupId].
|
||||
*/
|
||||
const filterProjectsByUserMembership = async (userId: string, groupId: string, projectIds: string[]) => {
|
||||
const userProjectMemberships: string[] = await db(TableName.ProjectMembership)
|
||||
.where(`${TableName.ProjectMembership}.userId`, userId)
|
||||
.whereIn(`${TableName.ProjectMembership}.projectId`, projectIds)
|
||||
.pluck(`${TableName.ProjectMembership}.projectId`);
|
||||
|
||||
return members.map(({ email, username, firstName, lastName, userId, publicKey, ...data }) => ({
|
||||
...data,
|
||||
user: { email, username, firstName, lastName, id: userId, publicKey }
|
||||
}));
|
||||
const userGroupMemberships: string[] = await db(TableName.UserGroupMembership)
|
||||
.where(`${TableName.UserGroupMembership}.userId`, userId)
|
||||
.whereNot(`${TableName.UserGroupMembership}.groupId`, groupId)
|
||||
.join(
|
||||
TableName.GroupProjectMembership,
|
||||
`${TableName.UserGroupMembership}.groupId`,
|
||||
`${TableName.GroupProjectMembership}.groupId`
|
||||
)
|
||||
.whereIn(`${TableName.GroupProjectMembership}.projectId`, projectIds)
|
||||
.pluck(`${TableName.GroupProjectMembership}.projectId`);
|
||||
|
||||
return new Set(userProjectMemberships.concat(userGroupMemberships));
|
||||
};
|
||||
|
||||
// special query
|
||||
const findUserGroupMembershipsInProject = async (usernames: string[], projectId: string) => {
|
||||
try {
|
||||
const usernameDocs: string[] = await db(TableName.UserGroupMembership)
|
||||
.join(
|
||||
TableName.GroupProjectMembership,
|
||||
`${TableName.UserGroupMembership}.groupId`,
|
||||
`${TableName.GroupProjectMembership}.groupId`
|
||||
)
|
||||
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
|
||||
.whereIn(`${TableName.Users}.username`, usernames) // TODO: pluck usernames
|
||||
.pluck(`${TableName.Users}.id`);
|
||||
|
||||
return usernameDocs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find group members in project" });
|
||||
throw new DatabaseError({ error, name: "Find user group members in project" });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,7 +58,7 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
|
||||
* Return list of users that are part of the group with id [groupId]
|
||||
* that have not yet been added individually to project with id [projectId].
|
||||
*
|
||||
* Filters out users that are part of other groups in the project.
|
||||
* Note: Filters out users that are part of other groups in the project.
|
||||
* @param groupId
|
||||
* @param projectId
|
||||
* @returns
|
||||
@@ -111,7 +118,8 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
|
||||
|
||||
return {
|
||||
...userGroupMembershipOrm,
|
||||
findGroupMembersInProject,
|
||||
filterProjectsByUserMembership,
|
||||
findUserGroupMembershipsInProject,
|
||||
findGroupMembersNotInProject
|
||||
};
|
||||
};
|
||||
|
||||
@@ -45,6 +45,44 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
|
||||
const getProjectPermission = async (userId: string, projectId: string) => {
|
||||
try {
|
||||
const groups: string[] = await db(TableName.GroupProjectMembership)
|
||||
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
|
||||
.pluck(`${TableName.GroupProjectMembership}.groupId`);
|
||||
|
||||
const groupDocs = await db(TableName.UserGroupMembership)
|
||||
.where(`${TableName.UserGroupMembership}.userId`, userId)
|
||||
.whereIn(`${TableName.UserGroupMembership}.groupId`, groups)
|
||||
.join(
|
||||
TableName.GroupProjectMembership,
|
||||
`${TableName.GroupProjectMembership}.groupId`,
|
||||
`${TableName.UserGroupMembership}.groupId`
|
||||
)
|
||||
.join(
|
||||
TableName.GroupProjectMembershipRole,
|
||||
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
|
||||
`${TableName.GroupProjectMembership}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.ProjectRoles,
|
||||
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
||||
`${TableName.ProjectRoles}.id`
|
||||
)
|
||||
.join(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||
.select(selectAllTableCols(TableName.GroupProjectMembershipRole))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
|
||||
// TODO(roll-forward-migration): remove this field when we drop this in next migration after a week
|
||||
db.ref("role").withSchema(TableName.GroupProjectMembership).as("oldRoleField"),
|
||||
db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("membershipCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("membershipUpdatedAt"),
|
||||
db.ref("projectId").withSchema(TableName.GroupProjectMembership),
|
||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||
db.ref("orgId").withSchema(TableName.Project),
|
||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
||||
)
|
||||
.select("permissions");
|
||||
|
||||
const docs = await db(TableName.ProjectMembership)
|
||||
.join(
|
||||
TableName.ProjectUserMembershipRole,
|
||||
@@ -67,17 +105,16 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
db.ref("role").withSchema(TableName.ProjectMembership).as("oldRoleField"),
|
||||
db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
|
||||
db.ref("projectId").withSchema(TableName.ProjectMembership),
|
||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||
db.ref("orgId").withSchema(TableName.Project),
|
||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
||||
)
|
||||
.select("permissions");
|
||||
|
||||
console.log("getProjectPermission docs: ", docs);
|
||||
|
||||
const permission = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "membershipId",
|
||||
data: docs.concat(groupDocs),
|
||||
key: "projectId",
|
||||
parentMapper: ({
|
||||
orgId,
|
||||
orgAuthEnforced,
|
||||
@@ -107,11 +144,48 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// const permission = sqlNestRelationships({
|
||||
// data: docs, // TODO: concat?
|
||||
// key: "membershipId",
|
||||
// parentMapper: ({
|
||||
// orgId,
|
||||
// orgAuthEnforced,
|
||||
// membershipId,
|
||||
// membershipCreatedAt,
|
||||
// membershipUpdatedAt,
|
||||
// oldRoleField,
|
||||
// // role
|
||||
// }) => ({
|
||||
// orgId,
|
||||
// orgAuthEnforced,
|
||||
// userId,
|
||||
// role: oldRoleField,
|
||||
// // role,
|
||||
// id: membershipId,
|
||||
// projectId,
|
||||
// createdAt: membershipCreatedAt,
|
||||
// updatedAt: membershipUpdatedAt
|
||||
// }),
|
||||
// childrenMapper: [
|
||||
// {
|
||||
// key: "id",
|
||||
// label: "roles" as const,
|
||||
// mapper: (data) =>
|
||||
// ProjectUserMembershipRolesSchema.extend({
|
||||
// permissions: z.unknown(),
|
||||
// customRoleSlug: z.string().optional().nullable()
|
||||
// }).parse(data)
|
||||
// }
|
||||
// ]
|
||||
// });
|
||||
|
||||
// when introducting cron mode change it here
|
||||
const activeRoles = permission?.[0]?.roles.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
);
|
||||
|
||||
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
||||
|
||||
@@ -218,6 +218,7 @@ const buildViewerPermissionRules = () => {
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||
|
||||
@@ -247,8 +247,12 @@ export const registerRoutes = async (
|
||||
const groupService = groupServiceFactory({
|
||||
userDAL,
|
||||
groupDAL,
|
||||
groupProjectDAL,
|
||||
orgDAL,
|
||||
userGroupMembershipDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
projectKeyDAL,
|
||||
permissionService,
|
||||
licenseService
|
||||
});
|
||||
@@ -362,6 +366,7 @@ export const registerRoutes = async (
|
||||
projectBotDAL,
|
||||
orgDAL,
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
smtpService,
|
||||
projectKeyDAL,
|
||||
projectRoleDAL,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
|
||||
import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal";
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TOrgDALFactory } from "../org/org-dal";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
@@ -45,6 +46,7 @@ type TProjectMembershipServiceFactoryDep = {
|
||||
projectMembershipDAL: TProjectMembershipDALFactory;
|
||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "find" | "delete">;
|
||||
userDAL: Pick<TUserDALFactory, "findById" | "findOne" | "findUserByProjectMembershipId" | "find">;
|
||||
userGroupMembershipDAL: TUserGroupMembershipDALFactory;
|
||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "findOrgMembersByUsername">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser" | "transaction">;
|
||||
@@ -63,6 +65,7 @@ export const projectMembershipServiceFactory = ({
|
||||
projectBotDAL,
|
||||
orgDAL,
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
projectDAL,
|
||||
projectKeyDAL,
|
||||
licenseService
|
||||
@@ -120,6 +123,13 @@ export const projectMembershipServiceFactory = ({
|
||||
});
|
||||
if (existingMembers.length) throw new BadRequestError({ message: "Some users are already part of project" });
|
||||
|
||||
const userIdsToExcludeForProjectKeyAddition = new Set(
|
||||
await userGroupMembershipDAL.findUserGroupMembershipsInProject(
|
||||
orgMembers.map(({ username }) => username),
|
||||
projectId
|
||||
)
|
||||
);
|
||||
|
||||
await projectMembershipDAL.transaction(async (tx) => {
|
||||
const projectMemberships = await projectMembershipDAL.insertMany(
|
||||
orgMembers.map(({ userId }) => ({
|
||||
@@ -135,13 +145,15 @@ export const projectMembershipServiceFactory = ({
|
||||
);
|
||||
const encKeyGroupByOrgMembId = groupBy(members, (i) => i.orgMembershipId);
|
||||
await projectKeyDAL.insertMany(
|
||||
orgMembers.map(({ userId, id }) => ({
|
||||
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
|
||||
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
|
||||
senderId: actorId,
|
||||
receiverId: userId as string,
|
||||
projectId
|
||||
})),
|
||||
orgMembers
|
||||
.filter(({ userId }) => !userIdsToExcludeForProjectKeyAddition.has(userId as string))
|
||||
.map(({ userId, id }) => ({
|
||||
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
|
||||
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
|
||||
senderId: actorId,
|
||||
receiverId: userId as string,
|
||||
projectId
|
||||
})),
|
||||
tx
|
||||
);
|
||||
});
|
||||
@@ -247,6 +259,10 @@ export const projectMembershipServiceFactory = ({
|
||||
|
||||
const members: TProjectMemberships[] = [];
|
||||
|
||||
const userIdsToExcludeForProjectKeyAddition = new Set(
|
||||
await userGroupMembershipDAL.findUserGroupMembershipsInProject(usernamesAndEmails, projectId)
|
||||
);
|
||||
|
||||
await projectMembershipDAL.transaction(async (tx) => {
|
||||
const projectMemberships = await projectMembershipDAL.insertMany(
|
||||
orgMembers.map(({ user }) => ({
|
||||
@@ -265,13 +281,15 @@ export const projectMembershipServiceFactory = ({
|
||||
|
||||
const encKeyGroupByOrgMembId = groupBy(newWsMembers, (i) => i.orgMembershipId);
|
||||
await projectKeyDAL.insertMany(
|
||||
orgMembers.map(({ user, id }) => ({
|
||||
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
|
||||
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
|
||||
senderId: ghostUser.id,
|
||||
receiverId: user.id,
|
||||
projectId
|
||||
})),
|
||||
orgMembers
|
||||
.filter(({ user }) => !userIdsToExcludeForProjectKeyAddition.has(user.id))
|
||||
.map(({ user, id }) => ({
|
||||
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
|
||||
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
|
||||
senderId: ghostUser.id,
|
||||
receiverId: user.id,
|
||||
projectId
|
||||
})),
|
||||
tx
|
||||
);
|
||||
});
|
||||
@@ -458,6 +476,10 @@ export const projectMembershipServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const userIdsToExcludeFromProjectKeyRemoval = new Set(
|
||||
await userGroupMembershipDAL.findUserGroupMembershipsInProject(usernamesAndEmails, projectId)
|
||||
);
|
||||
|
||||
const memberships = await projectMembershipDAL.transaction(async (tx) => {
|
||||
const deletedMemberships = await projectMembershipDAL.delete(
|
||||
{
|
||||
@@ -469,11 +491,15 @@ export const projectMembershipServiceFactory = ({
|
||||
tx
|
||||
);
|
||||
|
||||
// delete project keys belonging to users that are not part of any other groups in the project
|
||||
await projectKeyDAL.delete(
|
||||
{
|
||||
projectId,
|
||||
$in: {
|
||||
receiverId: projectMembers.map(({ user }) => user.id).filter(Boolean)
|
||||
receiverId: projectMembers
|
||||
.filter(({ user }) => !userIdsToExcludeFromProjectKeyRemoval.has(user.id))
|
||||
.map(({ user }) => user.id)
|
||||
.filter(Boolean)
|
||||
}
|
||||
},
|
||||
tx
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
faMoneyBill,
|
||||
faServer,
|
||||
faSignIn,
|
||||
faUser,
|
||||
faUserCog,
|
||||
faUsers
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -35,11 +36,17 @@ type Props = {
|
||||
|
||||
const SIMPLE_PERMISSION_OPTIONS = [
|
||||
{
|
||||
title: "Members management",
|
||||
subtitle: "Invite, view and remove members from the organization",
|
||||
icon: faUsers,
|
||||
title: "User management",
|
||||
subtitle: "Invite, view and remove users from the organization",
|
||||
icon: faUser,
|
||||
formName: "member"
|
||||
},
|
||||
{
|
||||
title: "Group management",
|
||||
subtitle: "Invite, view and remove user groups from the organization",
|
||||
icon: faUsers,
|
||||
formName: "groups"
|
||||
},
|
||||
{
|
||||
title: "Machine identity management",
|
||||
subtitle: "Create, view, update and remove (machine) identities from the organization",
|
||||
|
||||
@@ -28,6 +28,7 @@ export const formSchema = z.object({
|
||||
})
|
||||
.optional(),
|
||||
member: generalPermissionSchema,
|
||||
groups: generalPermissionSchema,
|
||||
role: generalPermissionSchema,
|
||||
settings: generalPermissionSchema,
|
||||
"service-account": generalPermissionSchema,
|
||||
|
||||
@@ -55,11 +55,17 @@ const SINGLE_PERMISSION_LIST = [
|
||||
formName: "role"
|
||||
},
|
||||
{
|
||||
title: "Project Members",
|
||||
subtitle: "Project members management control",
|
||||
title: "User management",
|
||||
subtitle: "Add, view and remove users from the project",
|
||||
icon: faUser,
|
||||
formName: "member"
|
||||
},
|
||||
{
|
||||
title: "Group management",
|
||||
subtitle: "Add, view and remove user groups from the project",
|
||||
icon: faUsers,
|
||||
formName: "groups"
|
||||
},
|
||||
{
|
||||
title: "Machine identity management",
|
||||
subtitle: "Add, view, update and remove (machine) identities from the project",
|
||||
|
||||
@@ -36,6 +36,7 @@ export const formSchema = z.object({
|
||||
.object({
|
||||
secrets: z.record(multiEnvPermissionSchema).optional(),
|
||||
member: generalPermissionSchema,
|
||||
groups: generalPermissionSchema,
|
||||
identity: generalPermissionSchema,
|
||||
role: generalPermissionSchema,
|
||||
integrations: generalPermissionSchema,
|
||||
|
||||
Reference in New Issue
Block a user