mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 18:28:12 +00:00
feat: fixing ts
This commit is contained in:
Vendored
-3
@@ -287,9 +287,6 @@ declare module "fastify" {
|
|||||||
telemetry: TTelemetryServiceFactory;
|
telemetry: TTelemetryServiceFactory;
|
||||||
dynamicSecret: TDynamicSecretServiceFactory;
|
dynamicSecret: TDynamicSecretServiceFactory;
|
||||||
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
|
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
|
||||||
projectUserAdditionalPrivilege: TProjectUserAdditionalPrivilegeServiceFactory;
|
|
||||||
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
|
|
||||||
identityProjectAdditionalPrivilegeV2: TIdentityProjectAdditionalPrivilegeV2ServiceFactory;
|
|
||||||
secretSharing: TSecretSharingServiceFactory;
|
secretSharing: TSecretSharingServiceFactory;
|
||||||
rateLimit: TRateLimitServiceFactory;
|
rateLimit: TRateLimitServiceFactory;
|
||||||
userEngagement: TUserEngagementServiceFactory;
|
userEngagement: TUserEngagementServiceFactory;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.string("status").defaultTo("invited");
|
t.string("status").defaultTo("invited");
|
||||||
t.string("inviteEmail");
|
t.string("inviteEmail");
|
||||||
t.datetime("lastInvitedAt");
|
t.datetime("lastInvitedAt");
|
||||||
t.datetime("lastLoginAuthMethod");
|
t.string("lastLoginAuthMethod");
|
||||||
t.datetime("lastLoginTime");
|
t.datetime("lastLoginTime");
|
||||||
t.specificType("projectFavorites", "text[]");
|
t.specificType("projectFavorites", "text[]");
|
||||||
t.timestamps(true, true, true);
|
t.timestamps(true, true, true);
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ export const MembershipsSchema = z.object({
|
|||||||
scopeOrgId: z.string().uuid(),
|
scopeOrgId: z.string().uuid(),
|
||||||
scopeProjectId: z.string().nullable().optional(),
|
scopeProjectId: z.string().nullable().optional(),
|
||||||
scopeNamespaceId: z.string().uuid().nullable().optional(),
|
scopeNamespaceId: z.string().uuid().nullable().optional(),
|
||||||
isActive: z.boolean().nullable().optional(),
|
isActive: z.boolean().default(true),
|
||||||
status: z.string().default("invited").nullable().optional(),
|
status: z.string().default("invited").nullable().optional(),
|
||||||
inviteEmail: z.string().nullable().optional(),
|
inviteEmail: z.string().nullable().optional(),
|
||||||
lastInvitedAt: z.date().nullable().optional(),
|
lastInvitedAt: z.date().nullable().optional(),
|
||||||
lastLoginAuthMethod: z.date().nullable().optional(),
|
lastLoginAuthMethod: z.string().nullable().optional(),
|
||||||
lastLoginTime: z.date().nullable().optional(),
|
lastLoginTime: z.date().nullable().optional(),
|
||||||
projectFavorites: z.string().array().nullable().optional(),
|
projectFavorites: z.string().array().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus, TableName } from "../schemas";
|
import { AccessScope, OrgMembershipRole, OrgMembershipStatus, TableName } from "../schemas";
|
||||||
import { seedData1 } from "../seed-data";
|
import { seedData1 } from "../seed-data";
|
||||||
|
|
||||||
export async function seed(knex: Knex): Promise<void> {
|
export async function seed(knex: Knex): Promise<void> {
|
||||||
@@ -24,13 +24,22 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
])
|
])
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
await knex(TableName.OrgMembership).insert([
|
const [membership] = await knex(TableName.Membership)
|
||||||
|
.insert([
|
||||||
|
{
|
||||||
|
scope: AccessScope.Organization,
|
||||||
|
scopeOrgId: org.id,
|
||||||
|
actorUserId: user.id,
|
||||||
|
isActive: true,
|
||||||
|
status: OrgMembershipStatus.Accepted
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
await knex(TableName.MembershipRole).insert([
|
||||||
{
|
{
|
||||||
role: OrgMembershipRole.Admin,
|
membershipId: membership.id,
|
||||||
orgId: org.id,
|
role: OrgMembershipRole.Admin
|
||||||
status: OrgMembershipStatus.Accepted,
|
|
||||||
userId: user.id,
|
|
||||||
isActive: true
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import { generateUserSrpKeys } from "@app/lib/crypto/srp";
|
|||||||
import { initLogger, logger } from "@app/lib/logger";
|
import { initLogger, logger } from "@app/lib/logger";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { AuthMethod } from "@app/services/auth/auth-type";
|
import { AuthMethod } from "@app/services/auth/auth-type";
|
||||||
|
import { membershipUserDALFactory } from "@app/services/membership-user/membership-user-dal";
|
||||||
import { assignWorkspaceKeysToMembers, createProjectKey } from "@app/services/project/project-fns";
|
import { assignWorkspaceKeysToMembers, createProjectKey } from "@app/services/project/project-fns";
|
||||||
import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
|
||||||
import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
|
|
||||||
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
||||||
import { userDALFactory } from "@app/services/user/user-dal";
|
import { userDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AccessScope,
|
||||||
OrgMembershipRole,
|
OrgMembershipRole,
|
||||||
OrgMembershipStatus,
|
OrgMembershipStatus,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
TableName
|
TableName
|
||||||
} from "../schemas";
|
} from "../schemas";
|
||||||
import { seedData1 } from "../seed-data";
|
import { seedData1 } from "../seed-data";
|
||||||
|
import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||||
|
|
||||||
export const DEFAULT_PROJECT_ENVS = [
|
export const DEFAULT_PROJECT_ENVS = [
|
||||||
{ name: "Development", slug: "dev" },
|
{ name: "Development", slug: "dev" },
|
||||||
@@ -39,8 +40,8 @@ const createUserWithGhostUser = async (
|
|||||||
) => {
|
) => {
|
||||||
const projectKeyDAL = projectKeyDALFactory(knex);
|
const projectKeyDAL = projectKeyDALFactory(knex);
|
||||||
const userDAL = userDALFactory(knex);
|
const userDAL = userDALFactory(knex);
|
||||||
const projectMembershipDAL = projectMembershipDALFactory(knex);
|
const membershipDAL = membershipUserDALFactory(knex);
|
||||||
const projectUserMembershipRoleDAL = projectUserMembershipRoleDALFactory(knex);
|
const membershipRoleDAL = membershipRoleDALFactory(knex);
|
||||||
|
|
||||||
const email = `sudo-${alphaNumericNanoId(16)}-${orgId}@infisical.com`; // We add a nanoid because the email is unique. And we have to create a new ghost user each time, so we can have access to the private key.
|
const email = `sudo-${alphaNumericNanoId(16)}-${orgId}@infisical.com`; // We add a nanoid because the email is unique. And we have to create a new ghost user each time, so we can have access to the private key.
|
||||||
|
|
||||||
@@ -63,25 +64,36 @@ const createUserWithGhostUser = async (
|
|||||||
.onConflict("userId")
|
.onConflict("userId")
|
||||||
.merge();
|
.merge();
|
||||||
|
|
||||||
await knex(TableName.OrgMembership)
|
const [orgMembership] = await knex(TableName.Membership)
|
||||||
.insert({
|
.insert({
|
||||||
orgId,
|
scope: AccessScope.Organization,
|
||||||
userId: ghostUser.id,
|
scopeOrgId: orgId,
|
||||||
role: OrgMembershipRole.Admin,
|
actorUserId: ghostUser.id,
|
||||||
status: OrgMembershipStatus.Accepted,
|
status: OrgMembershipStatus.Accepted,
|
||||||
isActive: true
|
isActive: true
|
||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
const [projectMembership] = await knex(TableName.ProjectMembership)
|
await knex(TableName.MembershipRole).insert([
|
||||||
|
{
|
||||||
|
membershipId: orgMembership.id,
|
||||||
|
role: OrgMembershipRole.Admin
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [projectMembership] = await knex(TableName.Membership)
|
||||||
.insert({
|
.insert({
|
||||||
userId: ghostUser.id,
|
actorUserId: ghostUser.id,
|
||||||
projectId
|
scopeProjectId: projectId,
|
||||||
|
scope: AccessScope.Project,
|
||||||
|
scopeOrgId: orgId,
|
||||||
|
status: OrgMembershipStatus.Accepted,
|
||||||
|
isActive: true
|
||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
await knex(TableName.ProjectUserMembershipRole).insert({
|
await knex(TableName.MembershipRole).insert({
|
||||||
projectMembershipId: projectMembership.id,
|
membershipId: projectMembership.id,
|
||||||
role: ProjectMembershipRole.Admin
|
role: ProjectMembershipRole.Admin
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -142,17 +154,16 @@ const createUserWithGhostUser = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create a membership for the user
|
// Create a membership for the user
|
||||||
const userProjectMembership = await projectMembershipDAL.create(
|
const userProjectMembership = await membershipDAL.create(
|
||||||
{
|
{
|
||||||
projectId,
|
scopeProjectId: projectId,
|
||||||
userId: user.id
|
scope: AccessScope.Project,
|
||||||
|
actorUserId: user.id,
|
||||||
|
scopeOrgId: orgId
|
||||||
},
|
},
|
||||||
knex
|
knex
|
||||||
);
|
);
|
||||||
await projectUserMembershipRoleDAL.create(
|
await membershipRoleDAL.create({ membershipId: userProjectMembership.id, role: ProjectMembershipRole.Admin }, knex);
|
||||||
{ projectMembershipId: userProjectMembership.id, role: ProjectMembershipRole.Admin },
|
|
||||||
knex
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create a project key for the user
|
// Create a project key for the user
|
||||||
await projectKeyDAL.create(
|
await projectKeyDAL.create(
|
||||||
@@ -195,10 +206,11 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
const userOrgMembership = await knex(TableName.OrgMembership)
|
const userOrgMembership = await knex(TableName.Membership)
|
||||||
.where({
|
.where({
|
||||||
orgId: seedData1.organization.id,
|
scopeOrgId: seedData1.organization.id,
|
||||||
userId: seedData1.id
|
actorUserId: seedData1.id,
|
||||||
|
scope: AccessScope.Organization
|
||||||
})
|
})
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectType, ProjectVersion, TableName } from "../schemas";
|
import { AccessScope, ProjectMembershipRole, ProjectType, ProjectVersion, TableName } from "../schemas";
|
||||||
import { seedData1 } from "../seed-data";
|
import { seedData1 } from "../seed-data";
|
||||||
|
|
||||||
export const DEFAULT_PROJECT_ENVS = [
|
export const DEFAULT_PROJECT_ENVS = [
|
||||||
@@ -23,15 +23,17 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
const projectMembershipV3 = await knex(TableName.ProjectMembership)
|
const projectMembershipV3 = await knex(TableName.Membership)
|
||||||
.insert({
|
.insert({
|
||||||
projectId: projectV2.id,
|
scopeProjectId: projectV2.id,
|
||||||
userId: seedData1.id
|
actorUserId: seedData1.id,
|
||||||
|
scope: AccessScope.Project,
|
||||||
|
scopeOrgId: seedData1.organization.id
|
||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
await knex(TableName.ProjectUserMembershipRole).insert({
|
await knex(TableName.MembershipRole).insert({
|
||||||
role: ProjectMembershipRole.Admin,
|
role: ProjectMembershipRole.Admin,
|
||||||
projectMembershipId: projectMembershipV3[0].id
|
membershipId: projectMembershipV3[0].id
|
||||||
});
|
});
|
||||||
|
|
||||||
// create default environments and default folders
|
// create default environments and default folders
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import { crypto } from "@app/lib/crypto/cryptography";
|
|||||||
import { initLogger, logger } from "@app/lib/logger";
|
import { initLogger, logger } from "@app/lib/logger";
|
||||||
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
||||||
|
|
||||||
import { IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas";
|
import { AccessScope, IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas";
|
||||||
import { seedData1 } from "../seed-data";
|
import { seedData1 } from "../seed-data";
|
||||||
|
|
||||||
export async function seed(knex: Knex): Promise<void> {
|
export async function seed(knex: Knex): Promise<void> {
|
||||||
// Deletes ALL existing entries
|
// Deletes ALL existing entries
|
||||||
await knex(TableName.Identity).del();
|
await knex(TableName.Identity).del();
|
||||||
await knex(TableName.IdentityOrgMembership).del();
|
|
||||||
|
|
||||||
initLogger();
|
initLogger();
|
||||||
|
|
||||||
@@ -78,34 +77,47 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
isClientSecretRevoked: false
|
isClientSecretRevoked: false
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
await knex(TableName.IdentityOrgMembership).insert([
|
const [orgMembership] = await knex(TableName.Membership)
|
||||||
|
.insert([
|
||||||
|
{
|
||||||
|
actorIdentityId: seedData1.machineIdentity.id,
|
||||||
|
scopeOrgId: seedData1.organization.id,
|
||||||
|
scope: AccessScope.Organization
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.returning("*");
|
||||||
|
await knex(TableName.MembershipRole).insert([
|
||||||
{
|
{
|
||||||
identityId: seedData1.machineIdentity.id,
|
membershipId: orgMembership.id,
|
||||||
orgId: seedData1.organization.id,
|
|
||||||
role: OrgMembershipRole.Admin
|
role: OrgMembershipRole.Admin
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const identityProjectMembership = await knex(TableName.IdentityProjectMembership)
|
const identityProjectMembership = await knex(TableName.Membership)
|
||||||
.insert({
|
.insert({
|
||||||
identityId: seedData1.machineIdentity.id,
|
actorIdentityId: seedData1.machineIdentity.id,
|
||||||
projectId: seedData1.project.id
|
scopeOrgId: seedData1.organization.id,
|
||||||
|
scope: AccessScope.Project,
|
||||||
|
scopeProjectId: seedData1.project.id
|
||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
await knex(TableName.IdentityProjectMembershipRole).insert({
|
await knex(TableName.MembershipRole).insert({
|
||||||
role: ProjectMembershipRole.Admin,
|
role: ProjectMembershipRole.Admin,
|
||||||
projectMembershipId: identityProjectMembership[0].id
|
membershipId: identityProjectMembership[0].id
|
||||||
});
|
});
|
||||||
const identityProjectMembershipV3 = await knex(TableName.IdentityProjectMembership)
|
|
||||||
|
const identityProjectMembershipV3 = await knex(TableName.Membership)
|
||||||
.insert({
|
.insert({
|
||||||
identityId: seedData1.machineIdentity.id,
|
actorIdentityId: seedData1.machineIdentity.id,
|
||||||
projectId: seedData1.projectV3.id
|
scopeOrgId: seedData1.organization.id,
|
||||||
|
scope: AccessScope.Project,
|
||||||
|
scopeProjectId: seedData1.projectV3.id
|
||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
await knex(TableName.IdentityProjectMembershipRole).insert({
|
await knex(TableName.MembershipRole).insert({
|
||||||
role: ProjectMembershipRole.Admin,
|
role: ProjectMembershipRole.Admin,
|
||||||
projectMembershipId: identityProjectMembershipV3[0].id
|
membershipId: identityProjectMembershipV3[0].id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,10 +78,7 @@ type TGithubOrgSyncServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
groupDAL: Pick<TGroupDALFactory, "insertMany" | "transaction" | "find">;
|
groupDAL: Pick<TGroupDALFactory, "insertMany" | "transaction" | "find">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
orgMembershipDAL: Pick<
|
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOrgMembershipsWithUsersByOrgId">;
|
||||||
TOrgMembershipDALFactory,
|
|
||||||
"find" | "findOrgMembershipById" | "findOrgMembershipsWithUsersByOrgId"
|
|
||||||
>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGithubOrgSyncServiceFactory = ReturnType<typeof githubOrgSyncServiceFactory>;
|
export type TGithubOrgSyncServiceFactory = ReturnType<typeof githubOrgSyncServiceFactory>;
|
||||||
|
|||||||
@@ -195,12 +195,36 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findOne = async (filter: Partial<TGroups>, tx?: Knex): Promise<TGroups | undefined> => {
|
||||||
|
try {
|
||||||
|
const doc = await (tx || db.replicaNode())(TableName.Groups)
|
||||||
|
.join(TableName.Membership, `${TableName.Membership}.actorGroupId`, `${TableName.Groups}.id`)
|
||||||
|
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
|
||||||
|
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
|
||||||
|
.where((queryBuilder) => {
|
||||||
|
Object.entries(filter).forEach(([key, value]) => {
|
||||||
|
void queryBuilder.where(`${TableName.Membership}.${key}`, value);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.select(
|
||||||
|
selectAllTableCols(TableName.Groups),
|
||||||
|
db.ref("role").withSchema(TableName.MembershipRole),
|
||||||
|
db.ref("customRoleId").as("roleId").withSchema(TableName.MembershipRole)
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
return doc;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find one" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...groupOrm,
|
...groupOrm,
|
||||||
findGroups,
|
findGroups,
|
||||||
findByOrgId,
|
findByOrgId,
|
||||||
findAllGroupPossibleMembers,
|
findAllGroupPossibleMembers,
|
||||||
findGroupsByProjectId,
|
findGroupsByProjectId,
|
||||||
findById
|
findById,
|
||||||
|
findOne
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { ProjectVersion, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
import { AccessScope, ProjectVersion, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, ScimRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, ScimRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ const addAcceptedUsersToGroup = async ({
|
|||||||
group,
|
group,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
@@ -42,13 +42,15 @@ const addAcceptedUsersToGroup = async ({
|
|||||||
const projectIds = Array.from(
|
const projectIds = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
(
|
(
|
||||||
await groupProjectDAL.find(
|
await membershipGroupDAL.find(
|
||||||
{
|
{
|
||||||
groupId: group.id
|
actorGroupId: group.id,
|
||||||
|
scopeOrgId: group.orgId,
|
||||||
|
scope: AccessScope.Project
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
)
|
)
|
||||||
).map((gp) => gp.projectId)
|
).map((gp) => gp.scopeProjectId as string)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -167,11 +169,11 @@ export const addUsersToGroupByUserIds = async ({
|
|||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
tx: outerTx
|
tx: outerTx,
|
||||||
|
membershipGroupDAL
|
||||||
}: TAddUsersToGroupByUserIds) => {
|
}: TAddUsersToGroupByUserIds) => {
|
||||||
const processAddition = async (tx: Knex) => {
|
const processAddition = async (tx: Knex) => {
|
||||||
const foundMembers = await userDAL.find(
|
const foundMembers = await userDAL.find(
|
||||||
@@ -214,15 +216,18 @@ export const addUsersToGroupByUserIds = async ({
|
|||||||
// check if all user(s) are part of the organization
|
// check if all user(s) are part of the organization
|
||||||
const existingUserOrgMemberships = await orgDAL.findMembership(
|
const existingUserOrgMemberships = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: group.orgId,
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: group.orgId,
|
||||||
|
scope: AccessScope.Organization,
|
||||||
$in: {
|
$in: {
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: userIds
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userIds
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
|
|
||||||
const existingUserOrgMembershipsUserIdsSet = new Set(existingUserOrgMemberships.map((u) => u.userId));
|
const existingUserOrgMembershipsUserIdsSet = new Set(
|
||||||
|
existingUserOrgMemberships.map((u) => u.actorUserId as string)
|
||||||
|
);
|
||||||
|
|
||||||
userIds.forEach((userId) => {
|
userIds.forEach((userId) => {
|
||||||
if (!existingUserOrgMembershipsUserIdsSet.has(userId))
|
if (!existingUserOrgMembershipsUserIdsSet.has(userId))
|
||||||
@@ -250,7 +255,7 @@ export const addUsersToGroupByUserIds = async ({
|
|||||||
group,
|
group,
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
@@ -292,9 +297,9 @@ export const removeUsersFromGroupByUserIds = async ({
|
|||||||
userIds,
|
userIds,
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
tx: outerTx
|
tx: outerTx,
|
||||||
|
membershipGroupDAL
|
||||||
}: TRemoveUsersFromGroupByUserIds) => {
|
}: TRemoveUsersFromGroupByUserIds) => {
|
||||||
const processRemoval = async (tx: Knex) => {
|
const processRemoval = async (tx: Knex) => {
|
||||||
const foundMembers = await userDAL.find({
|
const foundMembers = await userDAL.find({
|
||||||
@@ -352,13 +357,15 @@ export const removeUsersFromGroupByUserIds = async ({
|
|||||||
const projectIds = Array.from(
|
const projectIds = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
(
|
(
|
||||||
await groupProjectDAL.find(
|
await membershipGroupDAL.find(
|
||||||
{
|
{
|
||||||
groupId: group.id
|
scope: AccessScope.Project,
|
||||||
|
actorGroupId: group.id,
|
||||||
|
scopeOrgId: group.orgId
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
)
|
)
|
||||||
).map((gp) => gp.projectId)
|
).map((gp) => gp.scopeProjectId as string)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -422,11 +429,11 @@ export const convertPendingGroupAdditionsToGroupMemberships = async ({
|
|||||||
userIds,
|
userIds,
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
tx: outerTx
|
tx: outerTx,
|
||||||
|
membershipGroupDAL
|
||||||
}: TConvertPendingGroupAdditionsToGroupMemberships) => {
|
}: TConvertPendingGroupAdditionsToGroupMemberships) => {
|
||||||
const processConversion = async (tx: Knex) => {
|
const processConversion = async (tx: Knex) => {
|
||||||
const users = await userDAL.find(
|
const users = await userDAL.find(
|
||||||
@@ -463,7 +470,7 @@ export const convertPendingGroupAdditionsToGroupMemberships = async ({
|
|||||||
group: pendingGroupAddition.group,
|
group: pendingGroupAddition.group,
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
|
||||||
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
import { AccessScope, OrgMembershipRole, TRoles } from "@app/db/schemas";
|
||||||
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
||||||
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||||
|
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
@@ -35,8 +36,9 @@ type TGroupServiceFactoryDep = {
|
|||||||
TGroupDALFactory,
|
TGroupDALFactory,
|
||||||
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById" | "transaction"
|
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById" | "transaction"
|
||||||
>;
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find" | "findOne" | "create">;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers">;
|
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create" | "delete">;
|
||||||
|
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers" | "findById">;
|
||||||
userGroupMembershipDAL: Pick<
|
userGroupMembershipDAL: Pick<
|
||||||
TUserGroupMembershipDALFactory,
|
TUserGroupMembershipDALFactory,
|
||||||
"findOne" | "delete" | "filterProjectsByUserMembership" | "transaction" | "insertMany" | "find"
|
"findOne" | "delete" | "filterProjectsByUserMembership" | "transaction" | "insertMany" | "find"
|
||||||
@@ -54,7 +56,6 @@ export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
|
|||||||
export const groupServiceFactory = ({
|
export const groupServiceFactory = ({
|
||||||
userDAL,
|
userDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
groupProjectDAL,
|
|
||||||
orgDAL,
|
orgDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -62,12 +63,14 @@ export const groupServiceFactory = ({
|
|||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService,
|
licenseService,
|
||||||
oidcConfigDAL
|
oidcConfigDAL,
|
||||||
|
membershipGroupDAL,
|
||||||
|
membershipRoleDAL
|
||||||
}: TGroupServiceFactoryDep) => {
|
}: TGroupServiceFactoryDep) => {
|
||||||
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
|
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
|
||||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
@@ -82,25 +85,23 @@ export const groupServiceFactory = ({
|
|||||||
message: "Failed to create group due to plan restriction. Upgrade plan to create group."
|
message: "Failed to create group due to plan restriction. Upgrade plan to create group."
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
|
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([role], actorOrgId);
|
||||||
role,
|
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
||||||
actorOrgId
|
const isCustomRole = Boolean(rolePermissionDetails?.role);
|
||||||
);
|
|
||||||
const isCustomRole = Boolean(customRole);
|
|
||||||
if (role !== OrgMembershipRole.NoAccess) {
|
if (role !== OrgMembershipRole.NoAccess) {
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
shouldUseNewPrivilegeSystem,
|
||||||
OrgPermissionGroupActions.GrantPrivileges,
|
OrgPermissionGroupActions.GrantPrivileges,
|
||||||
OrgPermissionSubjects.Groups,
|
OrgPermissionSubjects.Groups,
|
||||||
permission,
|
permission,
|
||||||
rolePermission
|
rolePermissionDetails.permission
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!permissionBoundary.isValid)
|
if (!permissionBoundary.isValid)
|
||||||
throw new PermissionBoundaryError({
|
throw new PermissionBoundaryError({
|
||||||
message: constructPermissionErrorMessage(
|
message: constructPermissionErrorMessage(
|
||||||
"Failed to create group",
|
"Failed to create group",
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
shouldUseNewPrivilegeSystem,
|
||||||
OrgPermissionGroupActions.GrantPrivileges,
|
OrgPermissionGroupActions.GrantPrivileges,
|
||||||
OrgPermissionSubjects.Groups
|
OrgPermissionSubjects.Groups
|
||||||
),
|
),
|
||||||
@@ -122,7 +123,25 @@ export const groupServiceFactory = ({
|
|||||||
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
|
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
||||||
roleId: customRole?.id
|
roleId: rolePermissionDetails?.role?.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const membership = await membershipGroupDAL.create(
|
||||||
|
{
|
||||||
|
actorGroupId: newGroup.id,
|
||||||
|
scope: AccessScope.Organization,
|
||||||
|
scopeOrgId: actorOrgId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await membershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
membershipId: membership.id,
|
||||||
|
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
||||||
|
customRoleId: rolePermissionDetails?.role?.id
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -145,7 +164,7 @@ export const groupServiceFactory = ({
|
|||||||
}: TUpdateGroupDTO) => {
|
}: TUpdateGroupDTO) => {
|
||||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
@@ -166,32 +185,31 @@ export const groupServiceFactory = ({
|
|||||||
throw new NotFoundError({ message: `Failed to find group with ID ${id}` });
|
throw new NotFoundError({ message: `Failed to find group with ID ${id}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
let customRole: TOrgRoles | undefined;
|
let customRole: TRoles | undefined;
|
||||||
if (role) {
|
if (role) {
|
||||||
const { permission: rolePermission, role: customOrgRole } = await permissionService.getOrgPermissionByRole(
|
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([role], group.orgId);
|
||||||
role,
|
|
||||||
group.orgId
|
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
||||||
);
|
const isCustomRole = Boolean(rolePermissionDetails?.role);
|
||||||
|
|
||||||
const isCustomRole = Boolean(customOrgRole);
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
shouldUseNewPrivilegeSystem,
|
||||||
OrgPermissionGroupActions.GrantPrivileges,
|
OrgPermissionGroupActions.GrantPrivileges,
|
||||||
OrgPermissionSubjects.Groups,
|
OrgPermissionSubjects.Groups,
|
||||||
permission,
|
permission,
|
||||||
rolePermission
|
rolePermissionDetails.permission
|
||||||
);
|
);
|
||||||
if (!permissionBoundary.isValid)
|
if (!permissionBoundary.isValid)
|
||||||
throw new PermissionBoundaryError({
|
throw new PermissionBoundaryError({
|
||||||
message: constructPermissionErrorMessage(
|
message: constructPermissionErrorMessage(
|
||||||
"Failed to update group",
|
"Failed to update group",
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
shouldUseNewPrivilegeSystem,
|
||||||
OrgPermissionGroupActions.GrantPrivileges,
|
OrgPermissionGroupActions.GrantPrivileges,
|
||||||
OrgPermissionSubjects.Groups
|
OrgPermissionSubjects.Groups
|
||||||
),
|
),
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
});
|
});
|
||||||
if (isCustomRole) customRole = customOrgRole;
|
if (isCustomRole) customRole = rolePermissionDetails?.role;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
||||||
@@ -211,17 +229,31 @@ export const groupServiceFactory = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
slug: slug ? slugify(slug) : undefined,
|
slug: slug ? slugify(slug) : undefined
|
||||||
...(role
|
|
||||||
? {
|
|
||||||
role: customRole ? OrgMembershipRole.Custom : role,
|
|
||||||
roleId: customRole?.id ?? null
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (role) {
|
||||||
|
const membership = await membershipGroupDAL.findOne(
|
||||||
|
{
|
||||||
|
scope: AccessScope.Organization,
|
||||||
|
actorGroupId: updated.id,
|
||||||
|
scopeOrgId: updated.orgId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await membershipRoleDAL.delete({ membershipId: membership.id }, tx);
|
||||||
|
await membershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
membershipId: membership.id,
|
||||||
|
role: customRole ? OrgMembershipRole.Custom : role,
|
||||||
|
customRoleId: customRole?.id ?? null
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -341,7 +373,7 @@ export const groupServiceFactory = ({
|
|||||||
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
|
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
|
||||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
@@ -373,22 +405,23 @@ export const groupServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([group.role], actorOrgId);
|
||||||
|
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
||||||
|
|
||||||
// check if user has broader or equal to privileges than group
|
// check if user has broader or equal to privileges than group
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
shouldUseNewPrivilegeSystem,
|
||||||
OrgPermissionGroupActions.AddMembers,
|
OrgPermissionGroupActions.AddMembers,
|
||||||
OrgPermissionSubjects.Groups,
|
OrgPermissionSubjects.Groups,
|
||||||
permission,
|
permission,
|
||||||
groupRolePermission
|
rolePermissionDetails.permission
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!permissionBoundary.isValid)
|
if (!permissionBoundary.isValid)
|
||||||
throw new PermissionBoundaryError({
|
throw new PermissionBoundaryError({
|
||||||
message: constructPermissionErrorMessage(
|
message: constructPermissionErrorMessage(
|
||||||
"Failed to add user to more privileged group",
|
"Failed to add user to more privileged group",
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
shouldUseNewPrivilegeSystem,
|
||||||
OrgPermissionGroupActions.AddMembers,
|
OrgPermissionGroupActions.AddMembers,
|
||||||
OrgPermissionSubjects.Groups
|
OrgPermissionSubjects.Groups
|
||||||
),
|
),
|
||||||
@@ -407,7 +440,7 @@ export const groupServiceFactory = ({
|
|||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL
|
projectBotDAL
|
||||||
@@ -431,7 +464,7 @@ export const groupServiceFactory = ({
|
|||||||
}: TRemoveUserFromGroupDTO) => {
|
}: TRemoveUserFromGroupDTO) => {
|
||||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
@@ -463,21 +496,22 @@ export const groupServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([group.role], actorOrgId);
|
||||||
|
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
||||||
|
|
||||||
// check if user has broader or equal to privileges than group
|
// check if user has broader or equal to privileges than group
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
shouldUseNewPrivilegeSystem,
|
||||||
OrgPermissionGroupActions.RemoveMembers,
|
OrgPermissionGroupActions.RemoveMembers,
|
||||||
OrgPermissionSubjects.Groups,
|
OrgPermissionSubjects.Groups,
|
||||||
permission,
|
permission,
|
||||||
groupRolePermission
|
rolePermissionDetails.permission
|
||||||
);
|
);
|
||||||
if (!permissionBoundary.isValid)
|
if (!permissionBoundary.isValid)
|
||||||
throw new PermissionBoundaryError({
|
throw new PermissionBoundaryError({
|
||||||
message: constructPermissionErrorMessage(
|
message: constructPermissionErrorMessage(
|
||||||
"Failed to delete user from more privileged group",
|
"Failed to delete user from more privileged group",
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
shouldUseNewPrivilegeSystem,
|
||||||
OrgPermissionGroupActions.RemoveMembers,
|
OrgPermissionGroupActions.RemoveMembers,
|
||||||
OrgPermissionSubjects.Groups
|
OrgPermissionSubjects.Groups
|
||||||
),
|
),
|
||||||
@@ -495,7 +529,7 @@ export const groupServiceFactory = ({
|
|||||||
userIds: [user.id],
|
userIds: [user.id],
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
projectKeyDAL
|
projectKeyDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { TGroups } from "@app/db/schemas";
|
import { TGroups } from "@app/db/schemas";
|
||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
import { TGenericPermission } from "@app/lib/types";
|
import { TGenericPermission } from "@app/lib/types";
|
||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
@@ -63,7 +63,7 @@ export type TAddUsersToGroup = {
|
|||||||
group: TGroups;
|
group: TGroups;
|
||||||
userDAL: Pick<TUserDALFactory, "findUserEncKeyByUserIdsBatch">;
|
userDAL: Pick<TUserDALFactory, "findUserEncKeyByUserIdsBatch">;
|
||||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "transaction" | "insertMany">;
|
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "transaction" | "insertMany">;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
@@ -76,7 +76,7 @@ export type TAddUsersToGroupByUserIds = {
|
|||||||
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction">;
|
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction">;
|
||||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "transaction" | "insertMany">;
|
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "transaction" | "insertMany">;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership">;
|
orgDAL: Pick<TOrgDALFactory, "findMembership">;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
@@ -88,7 +88,7 @@ export type TRemoveUsersFromGroupByUserIds = {
|
|||||||
userIds: string[];
|
userIds: string[];
|
||||||
userDAL: Pick<TUserDALFactory, "find" | "transaction">;
|
userDAL: Pick<TUserDALFactory, "find" | "transaction">;
|
||||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "filterProjectsByUserMembership" | "delete">;
|
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "filterProjectsByUserMembership" | "delete">;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "delete">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "delete">;
|
||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
};
|
};
|
||||||
@@ -100,7 +100,7 @@ export type TConvertPendingGroupAdditionsToGroupMemberships = {
|
|||||||
TUserGroupMembershipDALFactory,
|
TUserGroupMembershipDALFactory,
|
||||||
"find" | "transaction" | "insertMany" | "deletePendingUserGroupMembershipsByUserIds"
|
"find" | "transaction" | "insertMany" | "deletePendingUserGroupMembershipsByUserIds"
|
||||||
>;
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
|
|||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
import { TDbClient } from "@app/db";
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { ormify } from "@app/lib/knex";
|
|
||||||
|
|
||||||
export type TIdentityProjectAdditionalPrivilegeV2DALFactory = ReturnType<
|
|
||||||
typeof identityProjectAdditionalPrivilegeV2DALFactory
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const identityProjectAdditionalPrivilegeV2DALFactory = (db: TDbClient) => {
|
|
||||||
const orm = ormify(db, TableName.IdentityProjectAdditionalPrivilege);
|
|
||||||
return orm;
|
|
||||||
};
|
|
||||||
-434
@@ -1,434 +0,0 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
|
||||||
import { packRules } from "@casl/ability/extra";
|
|
||||||
|
|
||||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
|
||||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
|
||||||
import { ms } from "@app/lib/ms";
|
|
||||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
|
||||||
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
|
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
|
||||||
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|
||||||
|
|
||||||
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
|
||||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "../permission/project-permission";
|
|
||||||
import { TIdentityProjectAdditionalPrivilegeV2DALFactory } from "./identity-project-additional-privilege-v2-dal";
|
|
||||||
import {
|
|
||||||
IdentityProjectAdditionalPrivilegeTemporaryMode,
|
|
||||||
TCreateIdentityPrivilegeDTO,
|
|
||||||
TDeleteIdentityPrivilegeByIdDTO,
|
|
||||||
TGetIdentityPrivilegeDetailsByIdDTO,
|
|
||||||
TGetIdentityPrivilegeDetailsBySlugDTO,
|
|
||||||
TListIdentityPrivilegesDTO,
|
|
||||||
TUpdateIdentityPrivilegeByIdDTO
|
|
||||||
} from "./identity-project-additional-privilege-v2-types";
|
|
||||||
|
|
||||||
type TIdentityProjectAdditionalPrivilegeV2ServiceFactoryDep = {
|
|
||||||
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeV2DALFactory;
|
|
||||||
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TIdentityProjectAdditionalPrivilegeV2ServiceFactory = ReturnType<
|
|
||||||
typeof identityProjectAdditionalPrivilegeV2ServiceFactory
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|
||||||
identityProjectAdditionalPrivilegeDAL,
|
|
||||||
identityProjectDAL,
|
|
||||||
projectDAL,
|
|
||||||
permissionService
|
|
||||||
}: TIdentityProjectAdditionalPrivilegeV2ServiceFactoryDep) => {
|
|
||||||
const create = async ({
|
|
||||||
slug,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorOrgId,
|
|
||||||
identityId,
|
|
||||||
permissions: customPermission,
|
|
||||||
actorAuthMethod,
|
|
||||||
...dto
|
|
||||||
}: TCreateIdentityPrivilegeDTO) => {
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
|
||||||
);
|
|
||||||
const { permission: targetIdentityPermission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor: ActorType.IDENTITY,
|
|
||||||
actorId: identityId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
|
||||||
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity,
|
|
||||||
permission,
|
|
||||||
targetIdentityPermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to update more privileged identity",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
validateHandlebarTemplate("Identity Additional Privilege Create", JSON.stringify(customPermission || []), {
|
|
||||||
allowedExpressions: (val) => val.includes("identity.")
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
||||||
slug,
|
|
||||||
projectMembershipId: identityProjectMembership.id
|
|
||||||
});
|
|
||||||
if (existingSlug) throw new BadRequestError({ message: "Additional privilege with provided slug already exists" });
|
|
||||||
|
|
||||||
const packedPermission = JSON.stringify(packRules(customPermission));
|
|
||||||
if (!dto.isTemporary) {
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
|
||||||
projectMembershipId: identityProjectMembership.id,
|
|
||||||
slug,
|
|
||||||
permissions: packedPermission
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...additionalPrivilege,
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
|
||||||
projectMembershipId: identityProjectMembership.id,
|
|
||||||
slug,
|
|
||||||
permissions: packedPermission,
|
|
||||||
isTemporary: true,
|
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
|
||||||
temporaryRange: dto.temporaryRange,
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateById = async ({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod
|
|
||||||
}: TUpdateIdentityPrivilegeByIdDTO) => {
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
|
|
||||||
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
|
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
|
||||||
});
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
|
||||||
);
|
|
||||||
const { permission: targetIdentityPermission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor: ActorType.IDENTITY,
|
|
||||||
actorId: identityProjectMembership.identityId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
|
||||||
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity,
|
|
||||||
permission,
|
|
||||||
targetIdentityPermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to update more privileged identity",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
validateHandlebarTemplate("Identity Additional Privilege Update", JSON.stringify(data.permissions || []), {
|
|
||||||
allowedExpressions: (val) => val.includes("identity.")
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data?.slug) {
|
|
||||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
||||||
slug: data.slug,
|
|
||||||
projectMembershipId: identityProjectMembership.id
|
|
||||||
});
|
|
||||||
if (existingSlug && existingSlug.id !== identityPrivilege.id)
|
|
||||||
throw new BadRequestError({ message: "Additional privilege with provided slug already exists" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
|
||||||
const packedPermission = data.permissions ? JSON.stringify(packRules(data.permissions)) : undefined;
|
|
||||||
if (isTemporary) {
|
|
||||||
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
|
||||||
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
|
||||||
slug: data.slug,
|
|
||||||
permissions: packedPermission,
|
|
||||||
isTemporary: data.isTemporary,
|
|
||||||
temporaryRange: data.temporaryRange,
|
|
||||||
temporaryMode: data.temporaryMode,
|
|
||||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
|
||||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...additionalPrivilege,
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
|
||||||
slug: data.slug,
|
|
||||||
permissions: packedPermission,
|
|
||||||
isTemporary: false,
|
|
||||||
temporaryAccessStartTime: null,
|
|
||||||
temporaryAccessEndTime: null,
|
|
||||||
temporaryRange: null,
|
|
||||||
temporaryMode: null
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...additionalPrivilege,
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteById = async ({ actorId, id, actor, actorOrgId, actorAuthMethod }: TDeleteIdentityPrivilegeByIdDTO) => {
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
|
|
||||||
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
|
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
|
||||||
});
|
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
|
||||||
);
|
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
|
|
||||||
actor: ActorType.IDENTITY,
|
|
||||||
actorId: identityProjectMembership.identityId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity,
|
|
||||||
permission,
|
|
||||||
identityRolePermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to update more privileged identity",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...deletedPrivilege,
|
|
||||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPrivilegeDetailsById = async ({
|
|
||||||
id,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod
|
|
||||||
}: TGetIdentityPrivilegeDetailsByIdDTO) => {
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
|
|
||||||
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
|
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
|
||||||
});
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...identityPrivilege,
|
|
||||||
permissions: unpackPermissions(identityPrivilege.permissions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPrivilegeDetailsBySlug = async ({
|
|
||||||
identityId,
|
|
||||||
slug,
|
|
||||||
projectSlug,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod
|
|
||||||
}: TGetIdentityPrivilegeDetailsBySlugDTO) => {
|
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug ${slug} not found` });
|
|
||||||
const projectId = project.id;
|
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
|
||||||
);
|
|
||||||
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
||||||
slug,
|
|
||||||
projectMembershipId: identityProjectMembership.id
|
|
||||||
});
|
|
||||||
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
|
|
||||||
|
|
||||||
return {
|
|
||||||
...identityPrivilege,
|
|
||||||
permissions: unpackPermissions(identityPrivilege.permissions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const listIdentityProjectPrivileges = async ({
|
|
||||||
identityId,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
projectId
|
|
||||||
}: TListIdentityPrivilegesDTO) => {
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
|
||||||
);
|
|
||||||
|
|
||||||
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find(
|
|
||||||
{
|
|
||||||
projectMembershipId: identityProjectMembership.id
|
|
||||||
},
|
|
||||||
{ sort: [[`${TableName.IdentityProjectAdditionalPrivilege}.slug` as "slug", "asc"]] }
|
|
||||||
);
|
|
||||||
return identityPrivileges;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
getPrivilegeDetailsById,
|
|
||||||
getPrivilegeDetailsBySlug,
|
|
||||||
listIdentityProjectPrivileges,
|
|
||||||
create,
|
|
||||||
updateById,
|
|
||||||
deleteById
|
|
||||||
};
|
|
||||||
};
|
|
||||||
-55
@@ -1,55 +0,0 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
|
||||||
|
|
||||||
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
|
||||||
|
|
||||||
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
|
||||||
Relative = "relative"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TCreateIdentityPrivilegeDTO = {
|
|
||||||
permissions: TProjectPermissionV2Schema[];
|
|
||||||
identityId: string;
|
|
||||||
projectId: string;
|
|
||||||
slug: string;
|
|
||||||
} & (
|
|
||||||
| {
|
|
||||||
isTemporary: false;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
isTemporary: true;
|
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
|
||||||
temporaryRange: string;
|
|
||||||
temporaryAccessStartTime: string;
|
|
||||||
}
|
|
||||||
) &
|
|
||||||
Omit<TProjectPermission, "projectId">;
|
|
||||||
|
|
||||||
export type TUpdateIdentityPrivilegeByIdDTO = { id: string } & Omit<TProjectPermission, "projectId"> & {
|
|
||||||
data: Partial<{
|
|
||||||
permissions: TProjectPermissionV2Schema[];
|
|
||||||
slug: string;
|
|
||||||
isTemporary: boolean;
|
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
|
||||||
temporaryRange: string;
|
|
||||||
temporaryAccessStartTime: string;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TDeleteIdentityPrivilegeByIdDTO = Omit<TProjectPermission, "projectId"> & {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TGetIdentityPrivilegeDetailsByIdDTO = Omit<TProjectPermission, "projectId"> & {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
|
|
||||||
identityId: string;
|
|
||||||
projectId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TGetIdentityPrivilegeDetailsBySlugDTO = Omit<TProjectPermission, "projectId"> & {
|
|
||||||
slug: string;
|
|
||||||
identityId: string;
|
|
||||||
projectSlug: string;
|
|
||||||
};
|
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
import { TDbClient } from "@app/db";
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { ormify } from "@app/lib/knex";
|
|
||||||
|
|
||||||
export type TIdentityProjectAdditionalPrivilegeDALFactory = ReturnType<
|
|
||||||
typeof identityProjectAdditionalPrivilegeDALFactory
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const identityProjectAdditionalPrivilegeDALFactory = (db: TDbClient) => {
|
|
||||||
const orm = ormify(db, TableName.IdentityProjectAdditionalPrivilege);
|
|
||||||
return orm;
|
|
||||||
};
|
|
||||||
-451
@@ -1,451 +0,0 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
|
|
||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
|
||||||
import { ms } from "@app/lib/ms";
|
|
||||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
|
||||||
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|
||||||
|
|
||||||
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
|
||||||
import {
|
|
||||||
ProjectPermissionIdentityActions,
|
|
||||||
ProjectPermissionSet,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "../permission/project-permission";
|
|
||||||
import { TIdentityProjectAdditionalPrivilegeDALFactory } from "./identity-project-additional-privilege-dal";
|
|
||||||
import {
|
|
||||||
IdentityProjectAdditionalPrivilegeTemporaryMode,
|
|
||||||
TCreateIdentityPrivilegeDTO,
|
|
||||||
TDeleteIdentityPrivilegeDTO,
|
|
||||||
TGetIdentityPrivilegeDetailsDTO,
|
|
||||||
TListIdentityPrivilegesDTO,
|
|
||||||
TUpdateIdentityPrivilegeDTO
|
|
||||||
} from "./identity-project-additional-privilege-types";
|
|
||||||
|
|
||||||
type TIdentityProjectAdditionalPrivilegeServiceFactoryDep = {
|
|
||||||
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeDALFactory;
|
|
||||||
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
|
|
||||||
typeof identityProjectAdditionalPrivilegeServiceFactory
|
|
||||||
>;
|
|
||||||
|
|
||||||
const unpackPermissions = (permissions: unknown) =>
|
|
||||||
UnpackedPermissionSchema.array().parse(
|
|
||||||
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
|
||||||
);
|
|
||||||
|
|
||||||
export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|
||||||
identityProjectAdditionalPrivilegeDAL,
|
|
||||||
identityProjectDAL,
|
|
||||||
permissionService,
|
|
||||||
projectDAL
|
|
||||||
}: TIdentityProjectAdditionalPrivilegeServiceFactoryDep) => {
|
|
||||||
const create = async ({
|
|
||||||
slug,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
identityId,
|
|
||||||
projectSlug,
|
|
||||||
permissions: customPermission,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
...dto
|
|
||||||
}: TCreateIdentityPrivilegeDTO) => {
|
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
|
||||||
const projectId = project.id;
|
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
|
||||||
);
|
|
||||||
|
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
|
||||||
actor: ActorType.IDENTITY,
|
|
||||||
actorId: identityId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
|
||||||
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity,
|
|
||||||
permission,
|
|
||||||
targetIdentityPermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to update more privileged identity",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
||||||
slug,
|
|
||||||
projectMembershipId: identityProjectMembership.id
|
|
||||||
});
|
|
||||||
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
|
||||||
|
|
||||||
validateHandlebarTemplate("Identity Additional Privilege Create", JSON.stringify(customPermission || []), {
|
|
||||||
allowedExpressions: (val) => val.includes("identity.")
|
|
||||||
});
|
|
||||||
|
|
||||||
const packedPermission = JSON.stringify(packRules(customPermission));
|
|
||||||
if (!dto.isTemporary) {
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
|
||||||
projectMembershipId: identityProjectMembership.id,
|
|
||||||
slug,
|
|
||||||
permissions: packedPermission
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...additionalPrivilege,
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
|
||||||
projectMembershipId: identityProjectMembership.id,
|
|
||||||
slug,
|
|
||||||
permissions: packedPermission,
|
|
||||||
isTemporary: true,
|
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
|
||||||
temporaryRange: dto.temporaryRange,
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateBySlug = async ({
|
|
||||||
projectSlug,
|
|
||||||
slug,
|
|
||||||
identityId,
|
|
||||||
data,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod
|
|
||||||
}: TUpdateIdentityPrivilegeDTO) => {
|
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
|
||||||
const projectId = project.id;
|
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
|
||||||
);
|
|
||||||
|
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
|
||||||
actor: ActorType.IDENTITY,
|
|
||||||
actorId: identityProjectMembership.identityId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
|
||||||
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity,
|
|
||||||
permission,
|
|
||||||
targetIdentityPermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to update more privileged identity",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
||||||
slug,
|
|
||||||
projectMembershipId: identityProjectMembership.id
|
|
||||||
});
|
|
||||||
if (!identityPrivilege) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (data?.slug) {
|
|
||||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
||||||
slug: data.slug,
|
|
||||||
projectMembershipId: identityProjectMembership.id
|
|
||||||
});
|
|
||||||
if (existingSlug && existingSlug.id !== identityPrivilege.id)
|
|
||||||
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
|
||||||
validateHandlebarTemplate("Identity Additional Privilege Update", JSON.stringify(data.permissions || []), {
|
|
||||||
allowedExpressions: (val) => val.includes("identity.")
|
|
||||||
});
|
|
||||||
|
|
||||||
const packedPermission = data.permissions ? JSON.stringify(packRules(data.permissions)) : undefined;
|
|
||||||
if (isTemporary) {
|
|
||||||
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
|
||||||
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
|
||||||
slug: data.slug,
|
|
||||||
permissions: packedPermission,
|
|
||||||
isTemporary: data.isTemporary,
|
|
||||||
temporaryRange: data.temporaryRange,
|
|
||||||
temporaryMode: data.temporaryMode,
|
|
||||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
|
||||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...additionalPrivilege,
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
|
||||||
slug: data.slug,
|
|
||||||
permissions: packedPermission,
|
|
||||||
isTemporary: false,
|
|
||||||
temporaryAccessStartTime: null,
|
|
||||||
temporaryAccessEndTime: null,
|
|
||||||
temporaryRange: null,
|
|
||||||
temporaryMode: null
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...additionalPrivilege,
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteBySlug = async ({
|
|
||||||
actorId,
|
|
||||||
slug,
|
|
||||||
identityId,
|
|
||||||
projectSlug,
|
|
||||||
actor,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod
|
|
||||||
}: TDeleteIdentityPrivilegeDTO) => {
|
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
|
||||||
const projectId = project.id;
|
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
|
||||||
);
|
|
||||||
|
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
|
|
||||||
actor: ActorType.IDENTITY,
|
|
||||||
actorId: identityProjectMembership.identityId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity,
|
|
||||||
permission,
|
|
||||||
identityRolePermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to edit more privileged identity",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Identity
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
||||||
slug,
|
|
||||||
projectMembershipId: identityProjectMembership.id
|
|
||||||
});
|
|
||||||
if (!identityPrivilege) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...deletedPrivilege,
|
|
||||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPrivilegeDetailsBySlug = async ({
|
|
||||||
projectSlug,
|
|
||||||
identityId,
|
|
||||||
slug,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod
|
|
||||||
}: TGetIdentityPrivilegeDetailsDTO) => {
|
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
|
||||||
const projectId = project.id;
|
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
|
||||||
);
|
|
||||||
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
||||||
slug,
|
|
||||||
projectMembershipId: identityProjectMembership.id
|
|
||||||
});
|
|
||||||
if (!identityPrivilege) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...identityPrivilege,
|
|
||||||
permissions: unpackPermissions(identityPrivilege.permissions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const listIdentityProjectPrivileges = async ({
|
|
||||||
identityId,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
projectSlug
|
|
||||||
}: TListIdentityPrivilegesDTO) => {
|
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
|
||||||
const projectId = project.id;
|
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
||||||
if (!identityProjectMembership)
|
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: identityProjectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionIdentityActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
|
||||||
);
|
|
||||||
|
|
||||||
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({
|
|
||||||
projectMembershipId: identityProjectMembership.id
|
|
||||||
});
|
|
||||||
return identityPrivileges.map((el) => ({
|
|
||||||
...el,
|
|
||||||
permissions: unpackPermissions(el.permissions)
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
create,
|
|
||||||
updateBySlug,
|
|
||||||
deleteBySlug,
|
|
||||||
getPrivilegeDetailsBySlug,
|
|
||||||
listIdentityProjectPrivileges
|
|
||||||
};
|
|
||||||
};
|
|
||||||
-56
@@ -1,56 +0,0 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
|
||||||
|
|
||||||
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
|
||||||
|
|
||||||
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
|
||||||
Relative = "relative"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TCreateIdentityPrivilegeDTO = {
|
|
||||||
permissions: TProjectPermissionV2Schema[];
|
|
||||||
identityId: string;
|
|
||||||
projectSlug: string;
|
|
||||||
slug: string;
|
|
||||||
} & (
|
|
||||||
| {
|
|
||||||
isTemporary: false;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
isTemporary: true;
|
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
|
||||||
temporaryRange: string;
|
|
||||||
temporaryAccessStartTime: string;
|
|
||||||
}
|
|
||||||
) &
|
|
||||||
Omit<TProjectPermission, "projectId">;
|
|
||||||
|
|
||||||
export type TUpdateIdentityPrivilegeDTO = { slug: string; identityId: string; projectSlug: string } & Omit<
|
|
||||||
TProjectPermission,
|
|
||||||
"projectId"
|
|
||||||
> & {
|
|
||||||
data: Partial<{
|
|
||||||
permissions: TProjectPermissionV2Schema[];
|
|
||||||
slug: string;
|
|
||||||
isTemporary: boolean;
|
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
|
||||||
temporaryRange: string;
|
|
||||||
temporaryAccessStartTime: string;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TDeleteIdentityPrivilegeDTO = Omit<TProjectPermission, "projectId"> & {
|
|
||||||
slug: string;
|
|
||||||
identityId: string;
|
|
||||||
projectSlug: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TGetIdentityPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & {
|
|
||||||
slug: string;
|
|
||||||
identityId: string;
|
|
||||||
projectSlug: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
|
|
||||||
identityId: string;
|
|
||||||
projectSlug: string;
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas";
|
import { AccessScope, OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas";
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
@@ -12,12 +12,12 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/
|
|||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||||
|
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
@@ -49,13 +49,13 @@ import { TLdapGroupMapDALFactory } from "./ldap-group-map-dal";
|
|||||||
type TLdapConfigServiceFactoryDep = {
|
type TLdapConfigServiceFactoryDep = {
|
||||||
ldapConfigDAL: Pick<TLdapConfigDALFactory, "create" | "update" | "findOne" | "transaction">;
|
ldapConfigDAL: Pick<TLdapConfigDALFactory, "create" | "update" | "findOne" | "transaction">;
|
||||||
ldapGroupMapDAL: Pick<TLdapGroupMapDALFactory, "find" | "create" | "delete" | "findLdapGroupMapsByLdapConfigId">;
|
ldapGroupMapDAL: Pick<TLdapGroupMapDALFactory, "find" | "create" | "delete" | "findLdapGroupMapsByLdapConfigId">;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
|
||||||
orgDAL: Pick<
|
orgDAL: Pick<
|
||||||
TOrgDALFactory,
|
TOrgDALFactory,
|
||||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||||
>;
|
>;
|
||||||
groupDAL: Pick<TGroupDALFactory, "find" | "findOne">;
|
groupDAL: Pick<TGroupDALFactory, "find" | "findOne">;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||||
|
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
@@ -87,9 +87,9 @@ export const ldapConfigServiceFactory = ({
|
|||||||
ldapConfigDAL,
|
ldapConfigDAL,
|
||||||
ldapGroupMapDAL,
|
ldapGroupMapDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
orgMembershipDAL,
|
|
||||||
groupDAL,
|
groupDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
|
membershipRoleDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
@@ -388,25 +388,33 @@ export const ldapConfigServiceFactory = ({
|
|||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
const [orgMembership] = await orgDAL.findMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: userAlias.userId,
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgDAL.createMembership(
|
const membership = await orgDAL.createMembership(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
actorUserId: userAlias.userId,
|
||||||
orgId,
|
scopeOrgId: orgId,
|
||||||
role,
|
scope: AccessScope.Organization,
|
||||||
roleId,
|
|
||||||
status: OrgMembershipStatus.Accepted,
|
status: OrgMembershipStatus.Accepted,
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
await membershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
membershipId: membership.id,
|
||||||
|
role,
|
||||||
|
customRoleId: roleId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
} else if (orgMembership.status === OrgMembershipStatus.Invited) {
|
} else if (orgMembership.status === OrgMembershipStatus.Invited) {
|
||||||
await orgDAL.updateMembershipById(
|
await orgDAL.updateMembershipById(
|
||||||
orgMembership.id,
|
orgMembership.id,
|
||||||
@@ -459,8 +467,9 @@ export const ldapConfigServiceFactory = ({
|
|||||||
|
|
||||||
const [orgMembership] = await orgDAL.findMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
@@ -469,16 +478,22 @@ export const ldapConfigServiceFactory = ({
|
|||||||
await throwOnPlanSeatLimitReached(licenseService, orgId, UserAliasType.LDAP);
|
await throwOnPlanSeatLimitReached(licenseService, orgId, UserAliasType.LDAP);
|
||||||
|
|
||||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
const membership = await orgDAL.createMembership(
|
||||||
await orgMembershipDAL.create(
|
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
actorUserId: newUser.id,
|
||||||
inviteEmail: email.toLowerCase(),
|
scopeOrgId: orgId,
|
||||||
orgId,
|
scope: AccessScope.Organization,
|
||||||
role,
|
|
||||||
roleId,
|
|
||||||
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true,
|
||||||
|
inviteEmail: email.toLowerCase()
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await membershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
membershipId: membership.id,
|
||||||
|
role,
|
||||||
|
customRoleId: roleId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -542,10 +557,10 @@ export const ldapConfigServiceFactory = ({
|
|||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
|
membershipGroupDAL,
|
||||||
tx
|
tx
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -566,7 +581,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
userIds: [newUser.id],
|
userIds: [newUser.id],
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
tx
|
tx
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -488,7 +488,7 @@ export const licenseServiceFactory = ({
|
|||||||
const getUsageMetrics = async (orgId: string) => {
|
const getUsageMetrics = async (orgId: string) => {
|
||||||
const [orgMembersUsed, identityUsed, projectCount] = await Promise.all([
|
const [orgMembersUsed, identityUsed, projectCount] = await Promise.all([
|
||||||
orgDAL.countAllOrgMembers(orgId),
|
orgDAL.countAllOrgMembers(orgId),
|
||||||
identityOrgMembershipDAL.countAllOrgIdentities({ orgId }),
|
identityOrgMembershipDAL.countAllOrgIdentities({ scopeOrgId: orgId }),
|
||||||
projectDAL.countOfOrgProjects(orgId)
|
projectDAL.countOfOrgProjects(orgId)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
|
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
|
||||||
|
|
||||||
import { OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
|
import { AccessScope, OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
|
||||||
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
|
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
|
||||||
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
@@ -19,12 +19,12 @@ import { OrgServiceActor } from "@app/lib/types";
|
|||||||
import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||||
|
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
@@ -62,7 +62,8 @@ type TOidcConfigServiceFactoryDep = {
|
|||||||
TOrgDALFactory,
|
TOrgDALFactory,
|
||||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||||
>;
|
>;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||||
|
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||||
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail" | "verify">;
|
smtpService: Pick<TSmtpService, "sendMail" | "verify">;
|
||||||
@@ -78,7 +79,6 @@ type TOidcConfigServiceFactoryDep = {
|
|||||||
| "delete"
|
| "delete"
|
||||||
| "filterProjectsByUserMembership"
|
| "filterProjectsByUserMembership"
|
||||||
>;
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
@@ -90,7 +90,6 @@ export type TOidcConfigServiceFactory = ReturnType<typeof oidcConfigServiceFacto
|
|||||||
|
|
||||||
export const oidcConfigServiceFactory = ({
|
export const oidcConfigServiceFactory = ({
|
||||||
orgDAL,
|
orgDAL,
|
||||||
orgMembershipDAL,
|
|
||||||
userDAL,
|
userDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
@@ -100,7 +99,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
oidcConfigDAL,
|
oidcConfigDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
|
membershipRoleDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
@@ -196,26 +196,33 @@ export const oidcConfigServiceFactory = ({
|
|||||||
const foundUser = await userDAL.findById(userAlias.userId, tx);
|
const foundUser = await userDAL.findById(userAlias.userId, tx);
|
||||||
const [orgMembership] = await orgDAL.findMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: foundUser.id,
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgMembershipDAL.create(
|
const membership = await orgDAL.createMembership(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
actorUserId: userAlias.userId,
|
||||||
inviteEmail: email,
|
scopeOrgId: orgId,
|
||||||
orgId,
|
scope: AccessScope.Organization,
|
||||||
role,
|
status: OrgMembershipStatus.Accepted,
|
||||||
roleId,
|
|
||||||
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
await membershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
membershipId: membership.id,
|
||||||
|
role,
|
||||||
|
customRoleId: roleId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
// Only update the membership to Accepted if the user account is already completed.
|
// Only update the membership to Accepted if the user account is already completed.
|
||||||
} else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
|
} else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
|
||||||
await orgDAL.updateMembershipById(
|
await orgDAL.updateMembershipById(
|
||||||
@@ -288,8 +295,9 @@ export const oidcConfigServiceFactory = ({
|
|||||||
|
|
||||||
const [orgMembership] = await orgDAL.findMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
@@ -299,15 +307,22 @@ export const oidcConfigServiceFactory = ({
|
|||||||
|
|
||||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgMembershipDAL.create(
|
const membership = await orgDAL.createMembership(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
actorUserId: newUser.id,
|
||||||
inviteEmail: email,
|
scopeOrgId: orgId,
|
||||||
orgId,
|
scope: AccessScope.Organization,
|
||||||
role,
|
|
||||||
roleId,
|
|
||||||
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true,
|
||||||
|
inviteEmail: email.toLowerCase()
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await membershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
membershipId: membership.id,
|
||||||
|
role,
|
||||||
|
customRoleId: roleId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -341,7 +356,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL
|
projectBotDAL
|
||||||
@@ -378,7 +393,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
group,
|
group,
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
projectKeyDAL
|
projectKeyDAL
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
-389
@@ -1,389 +0,0 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
|
||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
|
||||||
|
|
||||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
|
||||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
|
||||||
import { ms } from "@app/lib/ms";
|
|
||||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
|
||||||
|
|
||||||
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
|
|
||||||
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
|
||||||
import {
|
|
||||||
ProjectPermissionMemberActions,
|
|
||||||
ProjectPermissionSet,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "../permission/project-permission";
|
|
||||||
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
|
||||||
import {
|
|
||||||
ProjectUserAdditionalPrivilegeTemporaryMode,
|
|
||||||
TProjectUserAdditionalPrivilegeServiceFactory
|
|
||||||
} from "./project-user-additional-privilege-types";
|
|
||||||
|
|
||||||
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
|
||||||
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">;
|
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
|
|
||||||
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update">;
|
|
||||||
};
|
|
||||||
|
|
||||||
const unpackPermissions = (permissions: unknown) =>
|
|
||||||
UnpackedPermissionSchema.array().parse(
|
|
||||||
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
|
||||||
);
|
|
||||||
|
|
||||||
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|
||||||
projectUserAdditionalPrivilegeDAL,
|
|
||||||
projectMembershipDAL,
|
|
||||||
permissionService,
|
|
||||||
accessApprovalRequestDAL
|
|
||||||
}: TProjectUserAdditionalPrivilegeServiceFactoryDep): TProjectUserAdditionalPrivilegeServiceFactory => {
|
|
||||||
const create: TProjectUserAdditionalPrivilegeServiceFactory["create"] = async ({
|
|
||||||
slug,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
permissions: customPermission,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
projectMembershipId,
|
|
||||||
...dto
|
|
||||||
}) => {
|
|
||||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
|
||||||
if (!projectMembership)
|
|
||||||
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: projectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
|
||||||
const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor: ActorType.USER,
|
|
||||||
actorId: projectMembership.userId,
|
|
||||||
projectId: projectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
|
||||||
targetUserPermission.update(targetUserPermission.rules.concat(customPermission));
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionMemberActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Member,
|
|
||||||
permission,
|
|
||||||
targetUserPermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to update more privileged user",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionMemberActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Member
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
|
||||||
slug,
|
|
||||||
projectId: projectMembership.projectId,
|
|
||||||
userId: projectMembership.userId
|
|
||||||
});
|
|
||||||
if (existingSlug)
|
|
||||||
throw new BadRequestError({ message: `Additional privilege with provided slug ${slug} already exists` });
|
|
||||||
|
|
||||||
validateHandlebarTemplate("User Additional Privilege Create", JSON.stringify(customPermission || []), {
|
|
||||||
allowedExpressions: (val) => val.includes("identity.")
|
|
||||||
});
|
|
||||||
|
|
||||||
const packedPermission = JSON.stringify(packRules(customPermission));
|
|
||||||
if (!dto.isTemporary) {
|
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
|
||||||
userId: projectMembership.userId,
|
|
||||||
projectId: projectMembership.projectId,
|
|
||||||
slug,
|
|
||||||
permissions: packedPermission
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...additionalPrivilege,
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
|
||||||
projectId: projectMembership.projectId,
|
|
||||||
userId: projectMembership.userId,
|
|
||||||
slug,
|
|
||||||
permissions: packedPermission,
|
|
||||||
isTemporary: true,
|
|
||||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
|
||||||
temporaryRange: dto.temporaryRange,
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateById: TProjectUserAdditionalPrivilegeServiceFactory["updateById"] = async ({
|
|
||||||
privilegeId,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
...dto
|
|
||||||
}) => {
|
|
||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
|
||||||
if (!userPrivilege)
|
|
||||||
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
|
||||||
|
|
||||||
const projectMembership = await projectMembershipDAL.findOne({
|
|
||||||
userId: userPrivilege.userId,
|
|
||||||
projectId: userPrivilege.projectId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!projectMembership)
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
|
||||||
});
|
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: projectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
|
||||||
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
|
|
||||||
actor: ActorType.USER,
|
|
||||||
actorId: projectMembership.userId,
|
|
||||||
projectId: projectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
|
||||||
targetUserPermission.update(targetUserPermission.rules.concat(dto.permissions || []));
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionMemberActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Member,
|
|
||||||
permission,
|
|
||||||
targetUserPermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to update more privileged user",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionMemberActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Member
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (dto?.slug) {
|
|
||||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
|
||||||
slug: dto.slug,
|
|
||||||
userId: projectMembership.id,
|
|
||||||
projectId: projectMembership.projectId
|
|
||||||
});
|
|
||||||
if (existingSlug && existingSlug.id !== userPrivilege.id)
|
|
||||||
throw new BadRequestError({ message: `Additional privilege with provided slug ${dto.slug} already exists` });
|
|
||||||
}
|
|
||||||
|
|
||||||
validateHandlebarTemplate("User Additional Privilege Update", JSON.stringify(dto.permissions || []), {
|
|
||||||
allowedExpressions: (val) => val.includes("identity.")
|
|
||||||
});
|
|
||||||
|
|
||||||
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary;
|
|
||||||
|
|
||||||
const packedPermission = dto.permissions && JSON.stringify(packRules(dto.permissions));
|
|
||||||
if (isTemporary) {
|
|
||||||
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || userPrivilege?.temporaryAccessStartTime;
|
|
||||||
const temporaryRange = dto?.temporaryRange || userPrivilege?.temporaryRange;
|
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
|
||||||
slug: dto.slug,
|
|
||||||
permissions: packedPermission,
|
|
||||||
isTemporary: dto.isTemporary,
|
|
||||||
temporaryRange: dto.temporaryRange,
|
|
||||||
temporaryMode: dto.temporaryMode,
|
|
||||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
|
||||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...additionalPrivilege,
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
|
||||||
slug: dto.slug,
|
|
||||||
permissions: packedPermission,
|
|
||||||
isTemporary: false,
|
|
||||||
temporaryAccessStartTime: null,
|
|
||||||
temporaryAccessEndTime: null,
|
|
||||||
temporaryRange: null,
|
|
||||||
temporaryMode: null
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...additionalPrivilege,
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteById: TProjectUserAdditionalPrivilegeServiceFactory["deleteById"] = async ({
|
|
||||||
actorId,
|
|
||||||
actor,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
privilegeId
|
|
||||||
}) => {
|
|
||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
|
||||||
if (!userPrivilege)
|
|
||||||
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
|
||||||
|
|
||||||
const projectMembership = await projectMembershipDAL.findOne({
|
|
||||||
userId: userPrivilege.userId,
|
|
||||||
projectId: userPrivilege.projectId
|
|
||||||
});
|
|
||||||
if (!projectMembership)
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
|
||||||
});
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: projectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
|
||||||
|
|
||||||
// TODO(simp): look into this in the new ap service
|
|
||||||
await accessApprovalRequestDAL.update(
|
|
||||||
{
|
|
||||||
privilegeId: userPrivilege.id
|
|
||||||
},
|
|
||||||
{
|
|
||||||
privilegeDeletedAt: new Date(),
|
|
||||||
status: ApprovalStatus.REJECTED
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...deletedPrivilege,
|
|
||||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPrivilegeDetailsById: TProjectUserAdditionalPrivilegeServiceFactory["getPrivilegeDetailsById"] = async ({
|
|
||||||
privilegeId,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod
|
|
||||||
}) => {
|
|
||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
|
||||||
if (!userPrivilege)
|
|
||||||
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
|
||||||
|
|
||||||
const projectMembership = await projectMembershipDAL.findOne({
|
|
||||||
userId: userPrivilege.userId,
|
|
||||||
projectId: userPrivilege.projectId
|
|
||||||
});
|
|
||||||
if (!projectMembership)
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
|
||||||
});
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: projectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...userPrivilege,
|
|
||||||
permissions: unpackPermissions(userPrivilege.permissions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const listPrivileges: TProjectUserAdditionalPrivilegeServiceFactory["listPrivileges"] = async ({
|
|
||||||
projectMembershipId,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod
|
|
||||||
}) => {
|
|
||||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
|
||||||
if (!projectMembership)
|
|
||||||
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId: projectMembership.projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
|
||||||
|
|
||||||
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find(
|
|
||||||
{
|
|
||||||
userId: projectMembership.userId,
|
|
||||||
projectId: projectMembership.projectId
|
|
||||||
},
|
|
||||||
{ sort: [[`${TableName.ProjectUserAdditionalPrivilege}.slug` as "slug", "asc"]] }
|
|
||||||
);
|
|
||||||
return userPrivileges;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
create,
|
|
||||||
updateById,
|
|
||||||
deleteById,
|
|
||||||
getPrivilegeDetailsById,
|
|
||||||
listPrivileges
|
|
||||||
};
|
|
||||||
};
|
|
||||||
-60
@@ -1,60 +0,0 @@
|
|||||||
import { TProjectUserAdditionalPrivilege } from "@app/db/schemas";
|
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
|
||||||
|
|
||||||
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
|
||||||
|
|
||||||
export enum ProjectUserAdditionalPrivilegeTemporaryMode {
|
|
||||||
Relative = "relative"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TCreateUserPrivilegeDTO = (
|
|
||||||
| {
|
|
||||||
permissions: TProjectPermissionV2Schema[];
|
|
||||||
projectMembershipId: string;
|
|
||||||
slug: string;
|
|
||||||
isTemporary: false;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
permissions: TProjectPermissionV2Schema[];
|
|
||||||
projectMembershipId: string;
|
|
||||||
slug: string;
|
|
||||||
isTemporary: true;
|
|
||||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
|
||||||
temporaryRange: string;
|
|
||||||
temporaryAccessStartTime: string;
|
|
||||||
}
|
|
||||||
) &
|
|
||||||
Omit<TProjectPermission, "projectId">;
|
|
||||||
|
|
||||||
export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
|
|
||||||
Partial<{
|
|
||||||
permissions: TProjectPermissionV2Schema[];
|
|
||||||
slug: string;
|
|
||||||
isTemporary: boolean;
|
|
||||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
|
||||||
temporaryRange: string;
|
|
||||||
temporaryAccessStartTime: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type TDeleteUserPrivilegeDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
|
||||||
|
|
||||||
export type TGetUserPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
|
||||||
|
|
||||||
export type TListUserPrivilegesDTO = Omit<TProjectPermission, "projectId"> & { projectMembershipId: string };
|
|
||||||
|
|
||||||
interface TAdditionalPrivilege extends TProjectUserAdditionalPrivilege {
|
|
||||||
permissions: {
|
|
||||||
action: string[];
|
|
||||||
subject?: string | undefined;
|
|
||||||
conditions?: unknown;
|
|
||||||
inverted?: boolean | undefined;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TProjectUserAdditionalPrivilegeServiceFactory = {
|
|
||||||
create: (arg: TCreateUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
|
|
||||||
updateById: (arg: TUpdateUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
|
|
||||||
deleteById: (arg: TDeleteUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
|
|
||||||
getPrivilegeDetailsById: (arg: TGetUserPrivilegeDetailsDTO) => Promise<TAdditionalPrivilege>;
|
|
||||||
listPrivileges: (arg: TListUserPrivilegesDTO) => Promise<TProjectUserAdditionalPrivilege[]>;
|
|
||||||
};
|
|
||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
TGroups,
|
TGroups,
|
||||||
TSamlConfigs,
|
TSamlConfigs,
|
||||||
TSamlConfigsUpdate,
|
TSamlConfigsUpdate,
|
||||||
TUsers
|
TUsers,
|
||||||
|
AccessScope
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { throwOnPlanSeatLimitReached } from "@app/ee/services/license/license-fns";
|
import { throwOnPlanSeatLimitReached } from "@app/ee/services/license/license-fns";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
@@ -23,12 +24,9 @@ import { TGroupProjectDALFactory } from "@app/services/group-project/group-proje
|
|||||||
import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
|
import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
|
||||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
import { LoginMethod } from "@app/services/super-admin/super-admin-types";
|
import { LoginMethod } from "@app/services/super-admin/super-admin-types";
|
||||||
@@ -68,16 +66,7 @@ type TSamlConfigServiceFactoryDep = {
|
|||||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||||
>;
|
>;
|
||||||
identityMetadataDAL: Pick<TIdentityMetadataDALFactory, "delete" | "insertMany" | "transaction">;
|
identityMetadataDAL: Pick<TIdentityMetadataDALFactory, "delete" | "insertMany" | "transaction">;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
|
||||||
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "find" | "transaction">;
|
|
||||||
userGroupMembershipDAL: Pick<
|
|
||||||
TUserGroupMembershipDALFactory,
|
|
||||||
"find" | "delete" | "transaction" | "insertMany" | "filterProjectsByUserMembership"
|
|
||||||
>;
|
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser">;
|
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
|
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||||
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
||||||
@@ -88,7 +77,6 @@ type TSamlConfigServiceFactoryDep = {
|
|||||||
export const samlConfigServiceFactory = ({
|
export const samlConfigServiceFactory = ({
|
||||||
samlConfigDAL,
|
samlConfigDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
orgMembershipDAL,
|
|
||||||
userDAL,
|
userDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
@@ -102,7 +90,8 @@ export const samlConfigServiceFactory = ({
|
|||||||
tokenService,
|
tokenService,
|
||||||
smtpService,
|
smtpService,
|
||||||
identityMetadataDAL,
|
identityMetadataDAL,
|
||||||
kmsService
|
kmsService,
|
||||||
|
membershipRoleDAL
|
||||||
}: TSamlConfigServiceFactoryDep): TSamlConfigServiceFactory => {
|
}: TSamlConfigServiceFactoryDep): TSamlConfigServiceFactory => {
|
||||||
const parseSamlGroups = (groupsValue: string): string[] => {
|
const parseSamlGroups = (groupsValue: string): string[] => {
|
||||||
let samlGroups: string[] = [];
|
let samlGroups: string[] = [];
|
||||||
@@ -506,26 +495,35 @@ export const samlConfigServiceFactory = ({
|
|||||||
const foundUser = await userDAL.findById(userAlias.userId, tx);
|
const foundUser = await userDAL.findById(userAlias.userId, tx);
|
||||||
const [orgMembership] = await orgDAL.findMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: foundUser.id,
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgMembershipDAL.create(
|
const membership = await orgDAL.createMembership(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
actorUserId: userAlias.userId,
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
orgId,
|
scopeOrgId: orgId,
|
||||||
role,
|
scope: AccessScope.Organization,
|
||||||
roleId,
|
status: OrgMembershipStatus.Accepted,
|
||||||
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited,
|
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
await membershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
membershipId: membership.id,
|
||||||
|
role,
|
||||||
|
customRoleId: roleId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
// Only update the membership to Accepted if the user account is already completed.
|
// Only update the membership to Accepted if the user account is already completed.
|
||||||
} else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
|
} else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
|
||||||
await orgDAL.updateMembershipById(
|
await orgDAL.updateMembershipById(
|
||||||
@@ -606,8 +604,9 @@ export const samlConfigServiceFactory = ({
|
|||||||
|
|
||||||
const [orgMembership] = await orgDAL.findMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
@@ -617,15 +616,22 @@ export const samlConfigServiceFactory = ({
|
|||||||
|
|
||||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgMembershipDAL.create(
|
const membership = await orgDAL.createMembership(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
actorUserId: newUser.id,
|
||||||
inviteEmail: email,
|
scopeOrgId: orgId,
|
||||||
orgId,
|
scope: AccessScope.Organization,
|
||||||
role,
|
|
||||||
roleId,
|
|
||||||
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true,
|
||||||
|
inviteEmail: email.toLowerCase()
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await membershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
membershipId: membership.id,
|
||||||
|
role,
|
||||||
|
customRoleId: roleId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,15 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { scimPatch } from "scim-patch";
|
import { scimPatch } from "scim-patch";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas";
|
import {
|
||||||
|
AccessScope,
|
||||||
|
OrgMembershipRole,
|
||||||
|
OrgMembershipStatus,
|
||||||
|
TableName,
|
||||||
|
TGroups,
|
||||||
|
TMemberships,
|
||||||
|
TUsers
|
||||||
|
} from "@app/db/schemas";
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
@@ -13,12 +21,13 @@ import { BadRequestError, NotFoundError, ScimRequestError, UnauthorizedError } f
|
|||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||||
|
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||||
|
import { TMembershipUserDALFactory } from "@app/services/membership-user/membership-user-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { deleteOrgMembershipFn } from "@app/services/org/org-fns";
|
import { deleteOrgMembershipFn } from "@app/services/org/org-fns";
|
||||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { OrgAuthMethod } from "@app/services/org/org-types";
|
import { OrgAuthMethod } from "@app/services/org/org-types";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
@@ -33,7 +42,6 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
|
|||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
|
||||||
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
|
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
|
||||||
import { TScimGroup, TScimServiceFactory } from "./scim-types";
|
import { TScimGroup, TScimServiceFactory } from "./scim-types";
|
||||||
|
|
||||||
@@ -55,12 +63,9 @@ type TScimServiceFactoryDep = {
|
|||||||
| "updateMembershipById"
|
| "updateMembershipById"
|
||||||
| "findOrgById"
|
| "findOrgById"
|
||||||
>;
|
>;
|
||||||
orgMembershipDAL: Pick<
|
membershipUserDAL: TMembershipUserDALFactory;
|
||||||
TOrgMembershipDALFactory,
|
|
||||||
"find" | "findOne" | "create" | "updateById" | "findById" | "update"
|
|
||||||
>;
|
|
||||||
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser" | "findById">;
|
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser" | "findById">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId">;
|
||||||
groupDAL: Pick<
|
groupDAL: Pick<
|
||||||
TGroupDALFactory,
|
TGroupDALFactory,
|
||||||
| "create"
|
| "create"
|
||||||
@@ -72,7 +77,8 @@ type TScimServiceFactoryDep = {
|
|||||||
| "updateById"
|
| "updateById"
|
||||||
| "update"
|
| "update"
|
||||||
>;
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||||
|
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create" | "update">;
|
||||||
userGroupMembershipDAL: Pick<
|
userGroupMembershipDAL: Pick<
|
||||||
TUserGroupMembershipDALFactory,
|
TUserGroupMembershipDALFactory,
|
||||||
| "find"
|
| "find"
|
||||||
@@ -88,7 +94,6 @@ type TScimServiceFactoryDep = {
|
|||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
|
||||||
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
|
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,18 +103,18 @@ export const scimServiceFactory = ({
|
|||||||
userDAL,
|
userDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
orgMembershipDAL,
|
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
groupProjectDAL,
|
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectUserAdditionalPrivilegeDAL,
|
|
||||||
smtpService,
|
smtpService,
|
||||||
externalGroupOrgRoleMappingDAL
|
externalGroupOrgRoleMappingDAL,
|
||||||
|
membershipGroupDAL,
|
||||||
|
membershipUserDAL,
|
||||||
|
membershipRoleDAL
|
||||||
}: TScimServiceFactoryDep): TScimServiceFactory => {
|
}: TScimServiceFactoryDep): TScimServiceFactory => {
|
||||||
const createScimToken: TScimServiceFactory["createScimToken"] = async ({
|
const createScimToken: TScimServiceFactory["createScimToken"] = async ({
|
||||||
actor,
|
actor,
|
||||||
@@ -244,8 +249,9 @@ export const scimServiceFactory = ({
|
|||||||
const getScimUser: TScimServiceFactory["getScimUser"] = async ({ orgMembershipId, orgId }) => {
|
const getScimUser: TScimServiceFactory["getScimUser"] = async ({ orgMembershipId, orgId }) => {
|
||||||
const [membership] = await orgDAL
|
const [membership] = await orgDAL
|
||||||
.findMembership({
|
.findMembership({
|
||||||
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
|
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
@@ -322,13 +328,14 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
const { user: createdUser, orgMembership: createdOrgMembership } = await userDAL.transaction(async (tx) => {
|
const { user: createdUser, orgMembership: createdOrgMembership } = await userDAL.transaction(async (tx) => {
|
||||||
let user: TUsers | undefined;
|
let user: TUsers | undefined;
|
||||||
let orgMembership: TOrgMemberships;
|
let orgMembership: TMemberships;
|
||||||
if (userAlias) {
|
if (userAlias) {
|
||||||
user = await userDAL.findById(userAlias.userId, tx);
|
user = await userDAL.findById(userAlias.userId, tx);
|
||||||
orgMembership = await orgMembershipDAL.findOne(
|
orgMembership = await membershipUserDAL.findOne(
|
||||||
{
|
{
|
||||||
userId: user.id,
|
actorUserId: user.id,
|
||||||
orgId
|
scope: AccessScope.Organization,
|
||||||
|
scopeOrgId: orgId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -336,20 +343,24 @@ export const scimServiceFactory = ({
|
|||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
|
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
|
||||||
|
|
||||||
orgMembership = await orgMembershipDAL.create(
|
orgMembership = await membershipUserDAL.create(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
actorUserId: userAlias.userId,
|
||||||
inviteEmail: email.toLowerCase(),
|
inviteEmail: email.toLowerCase(),
|
||||||
orgId,
|
scopeOrgId: orgId,
|
||||||
role,
|
scope: AccessScope.Organization,
|
||||||
roleId,
|
|
||||||
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
await membershipRoleDAL.create({
|
||||||
|
membershipId: orgMembership.id,
|
||||||
|
role,
|
||||||
|
customRoleId: roleId
|
||||||
|
});
|
||||||
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
|
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
|
||||||
orgMembership = await orgMembershipDAL.updateById(
|
orgMembership = await membershipUserDAL.updateById(
|
||||||
orgMembership.id,
|
orgMembership.id,
|
||||||
{
|
{
|
||||||
status: OrgMembershipStatus.Accepted
|
status: OrgMembershipStatus.Accepted
|
||||||
@@ -401,8 +412,9 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
const [foundOrgMembership] = await orgDAL.findMembership(
|
const [foundOrgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
@@ -412,18 +424,25 @@ export const scimServiceFactory = ({
|
|||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
|
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
|
||||||
|
|
||||||
orgMembership = await orgMembershipDAL.create(
|
orgMembership = await membershipUserDAL.create(
|
||||||
{
|
{
|
||||||
userId: user.id,
|
actorUserId: user.id,
|
||||||
inviteEmail: email.toLowerCase(),
|
inviteEmail: email.toLowerCase(),
|
||||||
orgId,
|
scopeOrgId: orgId,
|
||||||
role,
|
scope: AccessScope.Organization,
|
||||||
roleId,
|
|
||||||
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
await membershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
membershipId: orgMembership.id,
|
||||||
|
role,
|
||||||
|
customRoleId: roleId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
// Only update the membership to Accepted if the user account is already completed.
|
// Only update the membership to Accepted if the user account is already completed.
|
||||||
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
|
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
|
||||||
orgMembership = await orgDAL.updateMembershipById(
|
orgMembership = await orgDAL.updateMembershipById(
|
||||||
@@ -475,8 +494,9 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
const [membership] = await orgDAL
|
const [membership] = await orgDAL
|
||||||
.findMembership({
|
.findMembership({
|
||||||
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
|
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
@@ -485,7 +505,7 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership)
|
if (!membership || !membership.actorUserId)
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
detail: "User not found",
|
detail: "User not found",
|
||||||
status: 404
|
status: 404
|
||||||
@@ -514,7 +534,7 @@ export const scimServiceFactory = ({
|
|||||||
org.orgAuthMethod === OrgAuthMethod.OIDC ? serverCfg.trustOidcEmails : serverCfg.trustSamlEmails;
|
org.orgAuthMethod === OrgAuthMethod.OIDC ? serverCfg.trustOidcEmails : serverCfg.trustSamlEmails;
|
||||||
|
|
||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
await orgMembershipDAL.updateById(
|
await membershipUserDAL.updateById(
|
||||||
membership.id,
|
membership.id,
|
||||||
{
|
{
|
||||||
isActive: scimUser.active
|
isActive: scimUser.active
|
||||||
@@ -523,7 +543,7 @@ export const scimServiceFactory = ({
|
|||||||
);
|
);
|
||||||
const hasEmailChanged = scimUser.emails[0].value !== membership.email;
|
const hasEmailChanged = scimUser.emails[0].value !== membership.email;
|
||||||
await userDAL.updateById(
|
await userDAL.updateById(
|
||||||
membership.userId,
|
membership.actorUserId as string,
|
||||||
{
|
{
|
||||||
firstName: scimUser.name.givenName,
|
firstName: scimUser.name.givenName,
|
||||||
email: scimUser.emails[0].value.toLowerCase(),
|
email: scimUser.emails[0].value.toLowerCase(),
|
||||||
@@ -556,8 +576,9 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
const [membership] = await orgDAL
|
const [membership] = await orgDAL
|
||||||
.findMembership({
|
.findMembership({
|
||||||
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
|
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
@@ -566,7 +587,7 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership)
|
if (!membership || !membership.actorUserId)
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
detail: "User not found",
|
detail: "User not found",
|
||||||
status: 404
|
status: 404
|
||||||
@@ -587,7 +608,7 @@ export const scimServiceFactory = ({
|
|||||||
{
|
{
|
||||||
orgId,
|
orgId,
|
||||||
aliasType: org.orgAuthMethod === OrgAuthMethod.OIDC ? UserAliasType.OIDC : UserAliasType.SAML,
|
aliasType: org.orgAuthMethod === OrgAuthMethod.OIDC ? UserAliasType.OIDC : UserAliasType.SAML,
|
||||||
userId: membership.userId
|
userId: membership.actorUserId as string
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
externalId
|
externalId
|
||||||
@@ -595,7 +616,7 @@ export const scimServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
await orgMembershipDAL.updateById(
|
await membershipUserDAL.updateById(
|
||||||
membership.id,
|
membership.id,
|
||||||
{
|
{
|
||||||
isActive: active
|
isActive: active
|
||||||
@@ -603,7 +624,7 @@ export const scimServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
await userDAL.updateById(
|
await userDAL.updateById(
|
||||||
membership.userId,
|
membership.actorUserId!,
|
||||||
{
|
{
|
||||||
firstName,
|
firstName,
|
||||||
email: email?.toLowerCase(),
|
email: email?.toLowerCase(),
|
||||||
@@ -628,8 +649,9 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
const deleteScimUser: TScimServiceFactory["deleteScimUser"] = async ({ orgMembershipId, orgId }) => {
|
const deleteScimUser: TScimServiceFactory["deleteScimUser"] = async ({ orgMembershipId, orgId }) => {
|
||||||
const [membership] = await orgDAL.findMembership({
|
const [membership] = await orgDAL.findMembership({
|
||||||
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
|
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership)
|
if (!membership)
|
||||||
@@ -647,13 +669,13 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
await deleteOrgMembershipFn({
|
await deleteOrgMembershipFn({
|
||||||
orgMembershipId: membership.id,
|
orgMembershipId: membership.id,
|
||||||
orgId: membership.orgId,
|
orgId: membership.scopeOrgId,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectUserAdditionalPrivilegeDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
licenseService
|
licenseService,
|
||||||
|
membershipUserDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
return {}; // intentionally return empty object upon success
|
return {}; // intentionally return empty object upon success
|
||||||
@@ -750,8 +772,9 @@ export const scimServiceFactory = ({
|
|||||||
if (!externalGroupMapping) return;
|
if (!externalGroupMapping) return;
|
||||||
|
|
||||||
// only get org memberships that are new (invites)
|
// only get org memberships that are new (invites)
|
||||||
const newOrgMemberships = await orgMembershipDAL.find({
|
const newOrgMemberships = await membershipUserDAL.find({
|
||||||
status: "invited",
|
status: "invited",
|
||||||
|
scope: AccessScope.Organization,
|
||||||
$in: {
|
$in: {
|
||||||
id: members.map((member) => member.value)
|
id: members.map((member) => member.value)
|
||||||
}
|
}
|
||||||
@@ -760,7 +783,7 @@ export const scimServiceFactory = ({
|
|||||||
if (!newOrgMemberships.length) return;
|
if (!newOrgMemberships.length) return;
|
||||||
|
|
||||||
// set new membership roles to group mapping value
|
// set new membership roles to group mapping value
|
||||||
await orgMembershipDAL.update(
|
await membershipRoleDAL.update(
|
||||||
{
|
{
|
||||||
$in: {
|
$in: {
|
||||||
id: newOrgMemberships.map((membership) => membership.id)
|
id: newOrgMemberships.map((membership) => membership.id)
|
||||||
@@ -768,7 +791,7 @@ export const scimServiceFactory = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: externalGroupMapping.role,
|
role: externalGroupMapping.role,
|
||||||
roleId: externalGroupMapping.roleId
|
customRoleId: externalGroupMapping.roleId
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -822,7 +845,8 @@ export const scimServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (members && members.length) {
|
if (members && members.length) {
|
||||||
const orgMemberships = await orgMembershipDAL.find({
|
const orgMemberships = await membershipUserDAL.find({
|
||||||
|
scope: AccessScope.Organization,
|
||||||
$in: {
|
$in: {
|
||||||
id: members.map((member) => member.value)
|
id: members.map((member) => member.value)
|
||||||
}
|
}
|
||||||
@@ -830,14 +854,14 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
const newMembers = await addUsersToGroupByUserIds({
|
const newMembers = await addUsersToGroupByUserIds({
|
||||||
group,
|
group,
|
||||||
userIds: orgMemberships.map((membership) => membership.userId as string),
|
userIds: orgMemberships.map((membership) => membership.actorUserId as string),
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
|
membershipGroupDAL,
|
||||||
tx
|
tx
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -850,9 +874,10 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const orgMemberships = await orgDAL.findMembership({
|
const orgMemberships = await orgDAL.findMembership({
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization,
|
||||||
$in: {
|
$in: {
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: newGroup.newMembers.map((member) => member.id)
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: newGroup.newMembers.map((member) => member.id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -895,9 +920,10 @@ export const scimServiceFactory = ({
|
|||||||
.then((g) => g.members);
|
.then((g) => g.members);
|
||||||
|
|
||||||
const orgMemberships = await orgDAL.findMembership({
|
const orgMemberships = await orgDAL.findMembership({
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization,
|
||||||
$in: {
|
$in: {
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: users
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: users
|
||||||
.filter((user) => user.isPartOfGroup)
|
.filter((user) => user.isPartOfGroup)
|
||||||
.map((user) => user.id)
|
.map((user) => user.id)
|
||||||
}
|
}
|
||||||
@@ -933,10 +959,10 @@ export const scimServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
||||||
if (group.name !== displayName) {
|
if (group?.name !== displayName) {
|
||||||
await externalGroupOrgRoleMappingDAL.update(
|
await externalGroupOrgRoleMappingDAL.update(
|
||||||
{
|
{
|
||||||
groupName: group.name,
|
groupName: group?.name,
|
||||||
orgId
|
orgId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -958,14 +984,16 @@ export const scimServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const orgMemberships = members.length
|
const orgMemberships = members.length
|
||||||
? await orgMembershipDAL.find({
|
? await membershipUserDAL.find({
|
||||||
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization,
|
||||||
$in: {
|
$in: {
|
||||||
id: members.map((member) => member.value)
|
id: members.map((member) => member.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.userId));
|
const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.actorUserId as string));
|
||||||
const userGroupMembers = await userGroupMembershipDAL.find({
|
const userGroupMembers = await userGroupMembershipDAL.find({
|
||||||
groupId: group.id
|
groupId: group.id
|
||||||
});
|
});
|
||||||
@@ -978,20 +1006,20 @@ export const scimServiceFactory = ({
|
|||||||
const allMembersUserIds = directMemberUserIds.concat(pendingGroupAdditionsUserIds);
|
const allMembersUserIds = directMemberUserIds.concat(pendingGroupAdditionsUserIds);
|
||||||
const allMembersUserIdsSet = new Set(allMembersUserIds);
|
const allMembersUserIdsSet = new Set(allMembersUserIds);
|
||||||
|
|
||||||
const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.userId as string));
|
const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.actorUserId as string));
|
||||||
const toRemoveUserIds = allMembersUserIds.filter((userId) => !membersIdsSet.has(userId));
|
const toRemoveUserIds = allMembersUserIds.filter((userId) => !membersIdsSet.has(userId));
|
||||||
|
|
||||||
if (toAddUserIds.length) {
|
if (toAddUserIds.length) {
|
||||||
await addUsersToGroupByUserIds({
|
await addUsersToGroupByUserIds({
|
||||||
group,
|
group,
|
||||||
userIds: toAddUserIds.map((member) => member.userId as string),
|
userIds: toAddUserIds.map((member) => member.actorUserId as string),
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
|
membershipGroupDAL,
|
||||||
tx
|
tx
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1002,7 +1030,7 @@ export const scimServiceFactory = ({
|
|||||||
userIds: toRemoveUserIds,
|
userIds: toRemoveUserIds,
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
membershipGroupDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
tx
|
tx
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AccessScope,
|
||||||
GroupProjectMembershipsSchema,
|
GroupProjectMembershipsSchema,
|
||||||
GroupsSchema,
|
GroupsSchema,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectUserMembershipRolesSchema,
|
ProjectUserMembershipRolesSchema,
|
||||||
|
TemporaryPermissionMode,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
|
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
|
||||||
@@ -13,7 +15,6 @@ import { ms } from "@app/lib/ms";
|
|||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
|
||||||
|
|
||||||
export const registerGroupProjectRouter = async (server: FastifyZodProvider) => {
|
export const registerGroupProjectRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -54,7 +55,7 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
|
|||||||
z.object({
|
z.object({
|
||||||
role: z.string(),
|
role: z.string(),
|
||||||
isTemporary: z.literal(true),
|
isTemporary: z.literal(true),
|
||||||
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
|
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
|
||||||
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
||||||
temporaryAccessStartTime: z.string().datetime()
|
temporaryAccessStartTime: z.string().datetime()
|
||||||
})
|
})
|
||||||
@@ -73,17 +74,27 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const groupMembership = await server.services.groupProject.addGroupToProject({
|
// TODO(simp): name to id convertor
|
||||||
actor: req.permission.type,
|
const { membership: groupMembership } = await server.services.membershipGroup.createMembership({
|
||||||
actorId: req.permission.id,
|
permission: req.permission,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
data: {
|
||||||
actorOrgId: req.permission.orgId,
|
groupId: req.params.groupIdOrName,
|
||||||
roles: req.body.roles || [{ role: req.body.role }],
|
roles: req.body.roles || [{ role: req.body.role, isTemporary: false }]
|
||||||
projectId: req.params.projectId,
|
},
|
||||||
groupIdOrName: req.params.groupIdOrName
|
scopeData: {
|
||||||
|
scope: AccessScope.Project,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { groupMembership };
|
return {
|
||||||
|
groupMembership: {
|
||||||
|
...groupMembership,
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
groupId: groupMembership.actorGroupId as string
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -115,7 +126,7 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
|
|||||||
z.object({
|
z.object({
|
||||||
role: z.string(),
|
role: z.string(),
|
||||||
isTemporary: z.literal(true),
|
isTemporary: z.literal(true),
|
||||||
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
|
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
|
||||||
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
||||||
temporaryAccessStartTime: z.string().datetime()
|
temporaryAccessStartTime: z.string().datetime()
|
||||||
})
|
})
|
||||||
@@ -131,17 +142,22 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const roles = await server.services.groupProject.updateGroupInProject({
|
const { membership: groupMembership } = await server.services.membershipGroup.updateMembership({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
selector: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
groupId: req.params.groupId
|
||||||
actorOrgId: req.permission.orgId,
|
},
|
||||||
projectId: req.params.projectId,
|
data: {
|
||||||
groupId: req.params.groupId,
|
roles: req.body.roles
|
||||||
roles: req.body.roles
|
},
|
||||||
|
scopeData: {
|
||||||
|
scope: AccessScope.Project,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { roles };
|
return { roles: groupMembership.roles.map((el) => ({ ...el, projectMembershipId: groupMembership.id })) };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -172,16 +188,25 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const groupMembership = await server.services.groupProject.removeGroupFromProject({
|
const { membership: groupMembership } = await server.services.membershipGroup.deleteMembership({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
selector: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
groupId: req.params.groupId
|
||||||
actorOrgId: req.permission.orgId,
|
},
|
||||||
groupId: req.params.groupId,
|
scopeData: {
|
||||||
projectId: req.params.projectId
|
scope: AccessScope.Project,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { groupMembership };
|
return {
|
||||||
|
groupMembership: {
|
||||||
|
...groupMembership,
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
groupId: groupMembership.actorGroupId as string
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -233,15 +258,17 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const groupMemberships = await server.services.groupProject.listGroupsInProject({
|
const { memberships: groupMemberships } = await server.services.membershipGroup.listMemberships({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
data: {},
|
||||||
actorAuthMethod: req.permission.authMethod,
|
scopeData: {
|
||||||
actorOrgId: req.permission.orgId,
|
scope: AccessScope.Project,
|
||||||
projectId: req.params.projectId
|
orgId: req.permission.orgId,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { groupMemberships };
|
return { groupMemberships: groupMemberships.map((el) => ({ ...el, groupId: el.actorGroupId as string })) };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -292,15 +319,25 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const groupMembership = await server.services.groupProject.getGroupInProject({
|
const { membership: groupMembership } = await server.services.membershipGroup.getMembershipByGroupId({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
selector: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
groupId: req.params.groupId
|
||||||
actorOrgId: req.permission.orgId,
|
},
|
||||||
...req.params
|
scopeData: {
|
||||||
|
scope: AccessScope.Project,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { groupMembership };
|
return {
|
||||||
|
groupMembership: {
|
||||||
|
...groupMembership,
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
groupId: groupMembership.actorGroupId as string
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AccessScope,
|
||||||
GroupProjectMembershipsSchema,
|
GroupProjectMembershipsSchema,
|
||||||
GroupsSchema,
|
GroupsSchema,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectUserMembershipRolesSchema,
|
ProjectUserMembershipRolesSchema,
|
||||||
|
TemporaryPermissionMode,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
|
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
|
||||||
@@ -13,7 +15,6 @@ import { ms } from "@app/lib/ms";
|
|||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
|
||||||
|
|
||||||
export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodProvider) => {
|
export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -54,7 +55,7 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
|
|||||||
z.object({
|
z.object({
|
||||||
role: z.string(),
|
role: z.string(),
|
||||||
isTemporary: z.literal(true),
|
isTemporary: z.literal(true),
|
||||||
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
|
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
|
||||||
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
||||||
temporaryAccessStartTime: z.string().datetime()
|
temporaryAccessStartTime: z.string().datetime()
|
||||||
})
|
})
|
||||||
@@ -73,17 +74,26 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const groupMembership = await server.services.groupProject.addGroupToProject({
|
const { membership: groupMembership } = await server.services.membershipGroup.createMembership({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
data: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
groupId: req.params.groupIdOrName,
|
||||||
actorOrgId: req.permission.orgId,
|
roles: req.body.roles || [{ role: req.body.role, isTemporary: false }]
|
||||||
roles: req.body.roles || [{ role: req.body.role }],
|
},
|
||||||
projectId: req.params.projectId,
|
scopeData: {
|
||||||
groupIdOrName: req.params.groupIdOrName
|
scope: AccessScope.Project,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { groupMembership };
|
return {
|
||||||
|
groupMembership: {
|
||||||
|
...groupMembership,
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
groupId: groupMembership.actorGroupId as string
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -115,7 +125,7 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
|
|||||||
z.object({
|
z.object({
|
||||||
role: z.string(),
|
role: z.string(),
|
||||||
isTemporary: z.literal(true),
|
isTemporary: z.literal(true),
|
||||||
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
|
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
|
||||||
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
||||||
temporaryAccessStartTime: z.string().datetime()
|
temporaryAccessStartTime: z.string().datetime()
|
||||||
})
|
})
|
||||||
@@ -131,17 +141,22 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const roles = await server.services.groupProject.updateGroupInProject({
|
const { membership: groupMembership } = await server.services.membershipGroup.updateMembership({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
selector: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
groupId: req.params.groupId
|
||||||
actorOrgId: req.permission.orgId,
|
},
|
||||||
projectId: req.params.projectId,
|
data: {
|
||||||
groupId: req.params.groupId,
|
roles: req.body.roles
|
||||||
roles: req.body.roles
|
},
|
||||||
|
scopeData: {
|
||||||
|
scope: AccessScope.Project,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { roles };
|
return { roles: groupMembership.roles.map((el) => ({ ...el, projectMembershipId: groupMembership.id })) };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -172,16 +187,25 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const groupMembership = await server.services.groupProject.removeGroupFromProject({
|
const { membership: groupMembership } = await server.services.membershipGroup.deleteMembership({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
selector: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
groupId: req.params.groupId
|
||||||
actorOrgId: req.permission.orgId,
|
},
|
||||||
groupId: req.params.groupId,
|
scopeData: {
|
||||||
projectId: req.params.projectId
|
scope: AccessScope.Project,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { groupMembership };
|
return {
|
||||||
|
groupMembership: {
|
||||||
|
...groupMembership,
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
groupId: groupMembership.actorGroupId as string
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -233,15 +257,17 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const groupMemberships = await server.services.groupProject.listGroupsInProject({
|
const { memberships: groupMemberships } = await server.services.membershipGroup.listMemberships({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
data: {},
|
||||||
actorAuthMethod: req.permission.authMethod,
|
scopeData: {
|
||||||
actorOrgId: req.permission.orgId,
|
scope: AccessScope.Project,
|
||||||
projectId: req.params.projectId
|
orgId: req.permission.orgId,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { groupMemberships };
|
return { groupMemberships: groupMemberships.map((el) => ({ ...el, groupId: el.actorGroupId as string })) };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -292,15 +318,25 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const groupMembership = await server.services.groupProject.getGroupInProject({
|
const { membership: groupMembership } = await server.services.membershipGroup.getMembershipByGroupId({
|
||||||
actor: req.permission.type,
|
permission: req.permission,
|
||||||
actorId: req.permission.id,
|
selector: {
|
||||||
actorAuthMethod: req.permission.authMethod,
|
groupId: req.params.groupId
|
||||||
actorOrgId: req.permission.orgId,
|
},
|
||||||
...req.params
|
scopeData: {
|
||||||
|
scope: AccessScope.Project,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { groupMembership };
|
return {
|
||||||
|
groupMembership: {
|
||||||
|
...groupMembership,
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
groupId: groupMembership.actorGroupId as string
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
|
import { AccessScope, TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
|
||||||
|
|
||||||
import { AuthModeJwtTokenPayload, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "../auth/auth-type";
|
import { AuthModeJwtTokenPayload, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "../auth/auth-type";
|
||||||
|
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TTokenDALFactory } from "./auth-token-dal";
|
import { TTokenDALFactory } from "./auth-token-dal";
|
||||||
import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types";
|
import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types";
|
||||||
@@ -14,7 +14,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo
|
|||||||
type TAuthTokenServiceFactoryDep = {
|
type TAuthTokenServiceFactoryDep = {
|
||||||
tokenDAL: TTokenDALFactory;
|
tokenDAL: TTokenDALFactory;
|
||||||
userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
|
userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOne">;
|
membershipUserDAL: Pick<TMembershipUserDALFactory, "findOne">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>;
|
export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>;
|
||||||
@@ -80,7 +80,7 @@ export const getTokenConfig = (tokenType: TokenType) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAuthTokenServiceFactoryDep) => {
|
export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL }: TAuthTokenServiceFactoryDep) => {
|
||||||
const createTokenForUser = async ({ type, userId, orgId, aliasId, payload }: TCreateTokenForUserDTO) => {
|
const createTokenForUser = async ({ type, userId, orgId, aliasId, payload }: TCreateTokenForUserDTO) => {
|
||||||
const { token, ...tkCfg } = getTokenConfig(type);
|
const { token, ...tkCfg } = getTokenConfig(type);
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@@ -208,9 +208,10 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
|
|||||||
if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` });
|
if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` });
|
||||||
|
|
||||||
if (token.organizationId) {
|
if (token.organizationId) {
|
||||||
const orgMembership = await orgMembershipDAL.findOne({
|
const orgMembership = await membershipUserDAL.findOne({
|
||||||
userId: user.id,
|
actorUserId: user.id,
|
||||||
orgId: token.organizationId
|
scopeOrgId: token.organizationId,
|
||||||
|
scope: AccessScope.Organization
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus, TableName, TUsers, UserDeviceSchema } from "@app/db/schemas";
|
import {
|
||||||
|
AccessScope,
|
||||||
|
OrgMembershipRole,
|
||||||
|
OrgMembershipStatus,
|
||||||
|
TableName,
|
||||||
|
TUsers,
|
||||||
|
UserDeviceSchema
|
||||||
|
} from "@app/db/schemas";
|
||||||
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
|
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
@@ -14,11 +21,12 @@ import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
|||||||
|
|
||||||
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
||||||
import { TokenType } from "../auth-token/auth-token-types";
|
import { TokenType } from "../auth-token/auth-token-types";
|
||||||
|
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
||||||
|
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
|
||||||
import { TNotificationServiceFactory } from "../notification/notification-service";
|
import { TNotificationServiceFactory } from "../notification/notification-service";
|
||||||
import { NotificationType } from "../notification/notification-types";
|
import { NotificationType } from "../notification/notification-types";
|
||||||
import { TOrgDALFactory } from "../org/org-dal";
|
import { TOrgDALFactory } from "../org/org-dal";
|
||||||
import { getDefaultOrgMembershipRole } from "../org/org-role-fns";
|
import { getDefaultOrgMembershipRole } from "../org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "../org-membership/org-membership-dal";
|
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { LoginMethod } from "../super-admin/super-admin-types";
|
import { LoginMethod } from "../super-admin/super-admin-types";
|
||||||
import { TTotpServiceFactory } from "../totp/totp-service";
|
import { TTotpServiceFactory } from "../totp/totp-service";
|
||||||
@@ -48,7 +56,8 @@ type TAuthLoginServiceFactoryDep = {
|
|||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
totpService: Pick<TTotpServiceFactory, "verifyUserTotp" | "verifyWithUserRecoveryCode">;
|
totpService: Pick<TTotpServiceFactory, "verifyUserTotp" | "verifyWithUserRecoveryCode">;
|
||||||
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||||
orgMembershipDAL: TOrgMembershipDALFactory;
|
membershipUserDAL: TMembershipUserDALFactory;
|
||||||
|
membershipRoleDAL: TMembershipRoleDALFactory;
|
||||||
notificationService: Pick<TNotificationServiceFactory, "createUserNotifications">;
|
notificationService: Pick<TNotificationServiceFactory, "createUserNotifications">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,10 +67,11 @@ export const authLoginServiceFactory = ({
|
|||||||
tokenService,
|
tokenService,
|
||||||
smtpService,
|
smtpService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
orgMembershipDAL,
|
|
||||||
totpService,
|
totpService,
|
||||||
auditLogService,
|
auditLogService,
|
||||||
notificationService
|
notificationService,
|
||||||
|
membershipUserDAL,
|
||||||
|
membershipRoleDAL
|
||||||
}: TAuthLoginServiceFactoryDep) => {
|
}: TAuthLoginServiceFactoryDep) => {
|
||||||
/*
|
/*
|
||||||
* Private
|
* Private
|
||||||
@@ -163,8 +173,8 @@ export const authLoginServiceFactory = ({
|
|||||||
if (organizationId) {
|
if (organizationId) {
|
||||||
const org = await orgDAL.findById(organizationId);
|
const org = await orgDAL.findById(organizationId);
|
||||||
if (org) {
|
if (org) {
|
||||||
await orgMembershipDAL.update(
|
await membershipUserDAL.update(
|
||||||
{ userId: user.id, orgId: org.id },
|
{ actorUserId: user.id, scopeOrgId: org.id, scope: AccessScope.Organization },
|
||||||
{ lastLoginAuthMethod: authMethod, lastLoginTime: new Date() }
|
{ lastLoginAuthMethod: authMethod, lastLoginTime: new Date() }
|
||||||
);
|
);
|
||||||
if (org.userTokenExpiration) {
|
if (org.userTokenExpiration) {
|
||||||
@@ -858,21 +868,34 @@ export const authLoginServiceFactory = ({
|
|||||||
}
|
}
|
||||||
orgId = defaultOrg.id;
|
orgId = defaultOrg.id;
|
||||||
const [orgMembership] = await orgDAL.findMembership({
|
const [orgMembership] = await orgDAL.findMembership({
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
const { role, roleId } = await getDefaultOrgMembershipRole(defaultOrg.defaultMembershipRole);
|
const { role, roleId } = await getDefaultOrgMembershipRole(defaultOrg.defaultMembershipRole);
|
||||||
|
|
||||||
await orgMembershipDAL.create({
|
await membershipUserDAL.transaction(async (tx) => {
|
||||||
userId: user.id,
|
const membership = await membershipUserDAL.create(
|
||||||
inviteEmail: email,
|
{
|
||||||
orgId,
|
actorUserId: user?.id,
|
||||||
role,
|
inviteEmail: email,
|
||||||
roleId,
|
scopeOrgId: orgId,
|
||||||
status: OrgMembershipStatus.Accepted,
|
scope: AccessScope.Organization,
|
||||||
isActive: true
|
status: OrgMembershipStatus.Accepted,
|
||||||
|
isActive: true
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await membershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
membershipId: membership.id,
|
||||||
|
role,
|
||||||
|
customRoleId: roleId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -895,10 +918,11 @@ export const authLoginServiceFactory = ({
|
|||||||
if (org) {
|
if (org) {
|
||||||
// checks for the membership and only sets the orgId / orgName if the user is a member of the specified org
|
// checks for the membership and only sets the orgId / orgName if the user is a member of the specified org
|
||||||
const orgMembership = await orgDAL.findMembership({
|
const orgMembership = await orgDAL.findMembership({
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: org.id,
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: org.id,
|
||||||
[`${TableName.OrgMembership}.isActive` as "isActive"]: true,
|
[`${TableName.Membership}.isActive` as "isActive"]: true,
|
||||||
[`${TableName.OrgMembership}.status` as "status"]: OrgMembershipStatus.Accepted
|
[`${TableName.Membership}.status` as "status"]: OrgMembershipStatus.Accepted,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
});
|
});
|
||||||
|
|
||||||
if (orgMembership) {
|
if (orgMembership) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { OrgMembershipStatus, TableName } from "@app/db/schemas";
|
import { AccessScope, OrgMembershipStatus, TableName } from "@app/db/schemas";
|
||||||
import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns";
|
import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns";
|
||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
@@ -8,17 +8,15 @@ import { crypto } from "@app/lib/crypto/cryptography";
|
|||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { getMinExpiresIn } from "@app/lib/fn";
|
import { getMinExpiresIn } from "@app/lib/fn";
|
||||||
import { isDisposableEmail } from "@app/lib/validator";
|
import { isDisposableEmail } from "@app/lib/validator";
|
||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
|
|
||||||
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
||||||
import { TokenType } from "../auth-token/auth-token-types";
|
import { TokenType } from "../auth-token/auth-token-types";
|
||||||
|
import { TMembershipGroupDALFactory } from "../membership-group/membership-group-dal";
|
||||||
import { TOrgDALFactory } from "../org/org-dal";
|
import { TOrgDALFactory } from "../org/org-dal";
|
||||||
import { TOrgServiceFactory } from "../org/org-service";
|
import { TOrgServiceFactory } from "../org/org-service";
|
||||||
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
|
||||||
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { getServerCfg } from "../super-admin/super-admin-service";
|
import { getServerCfg } from "../super-admin/super-admin-service";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
@@ -42,14 +40,12 @@ type TAuthSignupDep = {
|
|||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findProjectById" | "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findProjectById" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
|
||||||
orgService: Pick<TOrgServiceFactory, "createOrganization" | "findOrganizationById">;
|
orgService: Pick<TOrgServiceFactory, "createOrganization" | "findOrganizationById">;
|
||||||
orgDAL: TOrgDALFactory;
|
orgDAL: TOrgDALFactory;
|
||||||
tokenService: TAuthTokenServiceFactory;
|
tokenService: TAuthTokenServiceFactory;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "transaction" | "insertMany">;
|
membershipGroupDAL: TMembershipGroupDALFactory;
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAuthSignupFactory = ReturnType<typeof authSignupServiceFactory>;
|
export type TAuthSignupFactory = ReturnType<typeof authSignupServiceFactory>;
|
||||||
@@ -60,11 +56,11 @@ export const authSignupServiceFactory = ({
|
|||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
groupProjectDAL,
|
|
||||||
tokenService,
|
tokenService,
|
||||||
smtpService,
|
smtpService,
|
||||||
orgService,
|
orgService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
|
membershipGroupDAL,
|
||||||
licenseService
|
licenseService
|
||||||
}: TAuthSignupDep) => {
|
}: TAuthSignupDep) => {
|
||||||
// first step of signup. create user and send email
|
// first step of signup. create user and send email
|
||||||
@@ -200,9 +196,10 @@ export const authSignupServiceFactory = ({
|
|||||||
organizationId
|
organizationId
|
||||||
) {
|
) {
|
||||||
const [pendingOrgMembership] = await orgDAL.findMembership({
|
const [pendingOrgMembership] = await orgDAL.findMembership({
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
|
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
|
||||||
status: OrgMembershipStatus.Invited,
|
status: OrgMembershipStatus.Invited,
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: organizationId
|
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: organizationId,
|
||||||
|
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pendingOrgMembership) {
|
if (pendingOrgMembership) {
|
||||||
@@ -241,18 +238,18 @@ export const authSignupServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatedMembersips = await orgDAL.updateMembership(
|
const updatedMembersips = await orgDAL.updateMembership(
|
||||||
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited },
|
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited, scope: AccessScope.Organization },
|
||||||
{ userId: user.id, status: OrgMembershipStatus.Accepted }
|
{ actorUserId: user.id, status: OrgMembershipStatus.Accepted }
|
||||||
);
|
);
|
||||||
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
|
const uniqueOrgId = [...new Set(updatedMembersips.map(({ scopeOrgId }) => scopeOrgId))];
|
||||||
await Promise.allSettled(uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId)));
|
await Promise.allSettled(uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId)));
|
||||||
|
|
||||||
await convertPendingGroupAdditionsToGroupMemberships({
|
await convertPendingGroupAdditionsToGroupMemberships({
|
||||||
userIds: [user.id],
|
userIds: [user.id],
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
|
membershipGroupDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL
|
projectBotDAL
|
||||||
});
|
});
|
||||||
@@ -351,21 +348,21 @@ export const authSignupServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const updatedMembersips = await orgDAL.updateMembership(
|
const updatedMembersips = await orgDAL.updateMembership(
|
||||||
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited },
|
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited, scope: AccessScope.Organization },
|
||||||
{ userId: us.id, status: OrgMembershipStatus.Accepted },
|
{ actorUserId: us.id, status: OrgMembershipStatus.Accepted },
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
|
const uniqueOrgId = [...new Set(updatedMembersips.map(({ scopeOrgId }) => scopeOrgId))];
|
||||||
await Promise.allSettled(uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId, tx)));
|
await Promise.allSettled(uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId, tx)));
|
||||||
|
|
||||||
await convertPendingGroupAdditionsToGroupMemberships({
|
await convertPendingGroupAdditionsToGroupMemberships({
|
||||||
userIds: [user.id],
|
userIds: [user.id],
|
||||||
userDAL,
|
userDAL,
|
||||||
userGroupMembershipDAL,
|
userGroupMembershipDAL,
|
||||||
groupProjectDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
|
membershipGroupDAL,
|
||||||
tx
|
tx
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -1,19 +1,19 @@
|
|||||||
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
import { OrgMembershipRole, TRoles } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
|
||||||
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
|
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
|
||||||
|
|
||||||
|
import { TRoleDALFactory } from "../role/role-dal";
|
||||||
import { TExternalGroupOrgMembershipRoleMappingDTO } from "./external-group-org-role-mapping-types";
|
import { TExternalGroupOrgMembershipRoleMappingDTO } from "./external-group-org-role-mapping-types";
|
||||||
|
|
||||||
export const constructGroupOrgMembershipRoleMappings = async ({
|
export const constructGroupOrgMembershipRoleMappings = async ({
|
||||||
mappingsDTO,
|
mappingsDTO,
|
||||||
orgId,
|
orgId,
|
||||||
orgRoleDAL,
|
roleDAL,
|
||||||
licenseService
|
licenseService
|
||||||
}: {
|
}: {
|
||||||
mappingsDTO: TExternalGroupOrgMembershipRoleMappingDTO[];
|
mappingsDTO: TExternalGroupOrgMembershipRoleMappingDTO[];
|
||||||
orgRoleDAL: TOrgRoleDALFactory;
|
roleDAL: TRoleDALFactory;
|
||||||
licenseService: TLicenseServiceFactory;
|
licenseService: TLicenseServiceFactory;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -30,9 +30,9 @@ export const constructGroupOrgMembershipRoleMappings = async ({
|
|||||||
.filter((mapping) => isCustomOrgRole(mapping.roleSlug))
|
.filter((mapping) => isCustomOrgRole(mapping.roleSlug))
|
||||||
.map((mapping) => mapping.roleSlug);
|
.map((mapping) => mapping.roleSlug);
|
||||||
|
|
||||||
let customRolesMap: Map<string, TOrgRoles> = new Map();
|
let customRolesMap: Map<string, TRoles> = new Map();
|
||||||
if (customRoleSlugs.length > 0) {
|
if (customRoleSlugs.length > 0) {
|
||||||
const customRoles = await orgRoleDAL.find({
|
const customRoles = await roleDAL.find({
|
||||||
orgId,
|
orgId,
|
||||||
$in: {
|
$in: {
|
||||||
slug: customRoleSlugs
|
slug: customRoleSlugs
|
||||||
|
|||||||
+4
-4
@@ -6,15 +6,15 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
import { constructGroupOrgMembershipRoleMappings } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-fns";
|
import { constructGroupOrgMembershipRoleMappings } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-fns";
|
||||||
import { TSyncExternalGroupOrgMembershipRoleMappingsDTO } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-types";
|
import { TSyncExternalGroupOrgMembershipRoleMappingsDTO } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-types";
|
||||||
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
|
||||||
|
|
||||||
|
import { TRoleDALFactory } from "../role/role-dal";
|
||||||
import { TExternalGroupOrgRoleMappingDALFactory } from "./external-group-org-role-mapping-dal";
|
import { TExternalGroupOrgRoleMappingDALFactory } from "./external-group-org-role-mapping-dal";
|
||||||
|
|
||||||
type TExternalGroupOrgRoleMappingServiceFactoryDep = {
|
type TExternalGroupOrgRoleMappingServiceFactoryDep = {
|
||||||
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
|
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
licenseService: TLicenseServiceFactory;
|
licenseService: TLicenseServiceFactory;
|
||||||
orgRoleDAL: TOrgRoleDALFactory;
|
roleDAL: TRoleDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TExternalGroupOrgRoleMappingServiceFactory = ReturnType<typeof externalGroupOrgRoleMappingServiceFactory>;
|
export type TExternalGroupOrgRoleMappingServiceFactory = ReturnType<typeof externalGroupOrgRoleMappingServiceFactory>;
|
||||||
@@ -23,7 +23,7 @@ export const externalGroupOrgRoleMappingServiceFactory = ({
|
|||||||
externalGroupOrgRoleMappingDAL,
|
externalGroupOrgRoleMappingDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
permissionService,
|
permissionService,
|
||||||
orgRoleDAL
|
roleDAL
|
||||||
}: TExternalGroupOrgRoleMappingServiceFactoryDep) => {
|
}: TExternalGroupOrgRoleMappingServiceFactoryDep) => {
|
||||||
const listExternalGroupOrgRoleMappings = async (actor: OrgServiceActor) => {
|
const listExternalGroupOrgRoleMappings = async (actor: OrgServiceActor) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
@@ -61,7 +61,7 @@ export const externalGroupOrgRoleMappingServiceFactory = ({
|
|||||||
|
|
||||||
const mappings = await constructGroupOrgMembershipRoleMappings({
|
const mappings = await constructGroupOrgMembershipRoleMappings({
|
||||||
mappingsDTO: dto.mappings,
|
mappingsDTO: dto.mappings,
|
||||||
orgRoleDAL,
|
roleDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
orgId: actor.orgId
|
orgId: actor.orgId
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,504 +1,27 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType, ProjectMembershipRole, ProjectVersion, SecretKeyEncoding, TGroups } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TListProjectGroupUsersDTO } from "@app/ee/services/group/group-types";
|
import { TListProjectGroupUsersDTO } from "@app/ee/services/group/group-types";
|
||||||
import {
|
|
||||||
constructPermissionErrorMessage,
|
|
||||||
validatePrivilegeChangeOperation
|
|
||||||
} from "@app/ee/services/permission/permission-fns";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionGroupActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionGroupActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
|
||||||
import { groupBy } from "@app/lib/fn";
|
|
||||||
import { ms } from "@app/lib/ms";
|
|
||||||
import { isUuidV4 } from "@app/lib/validator";
|
|
||||||
|
|
||||||
import { TGroupDALFactory } from "../../ee/services/group/group-dal";
|
import { TGroupDALFactory } from "../../ee/services/group/group-dal";
|
||||||
import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal";
|
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
|
||||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
|
||||||
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
|
|
||||||
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
|
||||||
import { TGroupProjectDALFactory } from "./group-project-dal";
|
|
||||||
import { TGroupProjectMembershipRoleDALFactory } from "./group-project-membership-role-dal";
|
|
||||||
import {
|
|
||||||
TCreateProjectGroupDTO,
|
|
||||||
TDeleteProjectGroupDTO,
|
|
||||||
TGetGroupInProjectDTO,
|
|
||||||
TListProjectGroupDTO,
|
|
||||||
TUpdateProjectGroupDTO
|
|
||||||
} from "./group-project-types";
|
|
||||||
|
|
||||||
type TGroupProjectServiceFactoryDep = {
|
type TGroupProjectServiceFactoryDep = {
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "findOne" | "transaction" | "create" | "delete" | "findByProjectId">;
|
|
||||||
groupProjectMembershipRoleDAL: Pick<
|
|
||||||
TGroupProjectMembershipRoleDALFactory,
|
|
||||||
"create" | "transaction" | "insertMany" | "delete"
|
|
||||||
>;
|
|
||||||
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">;
|
groupDAL: Pick<TGroupDALFactory, "findOne" | "findAllGroupPossibleMembers">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles" | "invalidateProjectPermissionCache">;
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "findProjectGhostUser" | "findById">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGroupProjectServiceFactory = ReturnType<typeof groupProjectServiceFactory>;
|
export type TGroupProjectServiceFactory = ReturnType<typeof groupProjectServiceFactory>;
|
||||||
|
|
||||||
export const groupProjectServiceFactory = ({
|
export const groupProjectServiceFactory = ({
|
||||||
groupDAL,
|
groupDAL,
|
||||||
groupProjectDAL,
|
|
||||||
groupProjectMembershipRoleDAL,
|
|
||||||
userGroupMembershipDAL,
|
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectKeyDAL,
|
|
||||||
projectBotDAL,
|
|
||||||
projectRoleDAL,
|
|
||||||
permissionService
|
permissionService
|
||||||
}: TGroupProjectServiceFactoryDep) => {
|
}: TGroupProjectServiceFactoryDep) => {
|
||||||
const addGroupToProject = async ({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
roles,
|
|
||||||
projectId,
|
|
||||||
groupIdOrName
|
|
||||||
}: TCreateProjectGroupDTO) => {
|
|
||||||
const project = await projectDAL.findById(projectId);
|
|
||||||
|
|
||||||
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
|
|
||||||
if (project.version < 2) throw new BadRequestError({ message: `Failed to add group to E2EE project` });
|
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Create, ProjectPermissionSub.Groups);
|
|
||||||
|
|
||||||
let group: TGroups | null = null;
|
|
||||||
if (isUuidV4(groupIdOrName)) {
|
|
||||||
group = await groupDAL.findOne({ orgId: actorOrgId, id: groupIdOrName });
|
|
||||||
}
|
|
||||||
if (!group) {
|
|
||||||
group = await groupDAL.findOne({ orgId: actorOrgId, name: groupIdOrName });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!group) throw new NotFoundError({ message: `Failed to find group with ID or name ${groupIdOrName}` });
|
|
||||||
|
|
||||||
const existingGroup = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
|
|
||||||
if (existingGroup)
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Group with ID ${group.id} already exists in project with id ${project.id}`
|
|
||||||
});
|
|
||||||
|
|
||||||
for await (const { role: requestedRoleChange } of roles) {
|
|
||||||
const { permission: rolePermission } = await permissionService.getProjectPermissionByRole(
|
|
||||||
requestedRoleChange,
|
|
||||||
project.id
|
|
||||||
);
|
|
||||||
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionGroupActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Groups,
|
|
||||||
permission,
|
|
||||||
rolePermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to assign group to role",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionGroupActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Groups
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate custom roles input
|
|
||||||
const customInputRoles = roles.filter(
|
|
||||||
({ role }) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
|
|
||||||
);
|
|
||||||
const hasCustomRole = Boolean(customInputRoles.length);
|
|
||||||
const customRoles = hasCustomRole
|
|
||||||
? await projectRoleDAL.find({
|
|
||||||
projectId: project.id,
|
|
||||||
$in: { slug: customInputRoles.map(({ role }) => role) }
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (customRoles.length !== customInputRoles.length) {
|
|
||||||
const customRoleSlugs = customRoles.map((customRole) => customRole.slug);
|
|
||||||
const missingInputRoles = customInputRoles
|
|
||||||
.filter((inputRole) => !customRoleSlugs.includes(inputRole.role))
|
|
||||||
.map((role) => role.role);
|
|
||||||
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Custom role/s not found: ${missingInputRoles.join(", ")}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
|
||||||
|
|
||||||
const projectGroup = await groupProjectDAL.transaction(async (tx) => {
|
|
||||||
const groupProjectMembership = await groupProjectDAL.create(
|
|
||||||
{
|
|
||||||
groupId: group!.id,
|
|
||||||
projectId: project.id
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
const sanitizedProjectMembershipRoles = roles.map((inputRole) => {
|
|
||||||
const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]);
|
|
||||||
if (!inputRole.isTemporary) {
|
|
||||||
return {
|
|
||||||
projectMembershipId: groupProjectMembership.id,
|
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
|
||||||
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// check cron or relative here later for now its just relative
|
|
||||||
const relativeTimeInMs = ms(inputRole.temporaryRange);
|
|
||||||
return {
|
|
||||||
projectMembershipId: groupProjectMembership.id,
|
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
|
||||||
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null,
|
|
||||||
isTemporary: true,
|
|
||||||
temporaryMode: ProjectUserMembershipTemporaryMode.Relative,
|
|
||||||
temporaryRange: inputRole.temporaryRange,
|
|
||||||
temporaryAccessStartTime: new Date(inputRole.temporaryAccessStartTime),
|
|
||||||
temporaryAccessEndTime: new Date(new Date(inputRole.temporaryAccessStartTime).getTime() + relativeTimeInMs)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
await groupProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
|
||||||
|
|
||||||
// share project key with users in group that have not
|
|
||||||
// individually been added to the project and that are not part of
|
|
||||||
// other groups that are in the project
|
|
||||||
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group!.id, project.id, tx);
|
|
||||||
|
|
||||||
if (groupMembers.length && (project.version === ProjectVersion.V1 || project.version === ProjectVersion.V2)) {
|
|
||||||
const ghostUser = await projectDAL.findProjectGhostUser(project.id, tx);
|
|
||||||
|
|
||||||
if (!ghostUser) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Failed to find project owner of project with name ${project.name}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, project.id, tx);
|
|
||||||
|
|
||||||
if (!ghostUserLatestKey) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Failed to find project owner's latest key in project with name ${project.name}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ghostUserLatestKey.sender.publicKey) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Failed to find project owner's latest key in project with name ${project.name}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const bot = await projectBotDAL.findOne({ projectId: project.id }, tx);
|
|
||||||
|
|
||||||
if (!bot) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Failed to find project bot in project with name ${project.name}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const botPrivateKey = crypto
|
|
||||||
.encryption()
|
|
||||||
.symmetric()
|
|
||||||
.decryptWithRootEncryptionKey({
|
|
||||||
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
|
||||||
iv: bot.iv,
|
|
||||||
tag: bot.tag,
|
|
||||||
ciphertext: bot.encryptedPrivateKey
|
|
||||||
});
|
|
||||||
|
|
||||||
const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({
|
|
||||||
ciphertext: ghostUserLatestKey.encryptedKey,
|
|
||||||
nonce: ghostUserLatestKey.nonce,
|
|
||||||
publicKey: ghostUserLatestKey.sender.publicKey,
|
|
||||||
privateKey: botPrivateKey
|
|
||||||
});
|
|
||||||
|
|
||||||
const projectKeyData = groupMembers.map(({ user: { publicKey, id } }) => {
|
|
||||||
if (!publicKey) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Failed to find user's public key in project with name ${project.name}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { ciphertext: encryptedKey, nonce } = crypto
|
|
||||||
.encryption()
|
|
||||||
.asymmetric()
|
|
||||||
.encrypt(plaintextProjectKey, publicKey, botPrivateKey);
|
|
||||||
|
|
||||||
return {
|
|
||||||
encryptedKey,
|
|
||||||
nonce,
|
|
||||||
senderId: ghostUser.id,
|
|
||||||
receiverId: id,
|
|
||||||
projectId: project.id
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
await projectKeyDAL.insertMany(projectKeyData, tx);
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupProjectMembership;
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
|
||||||
|
|
||||||
return projectGroup;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateGroupInProject = async ({
|
|
||||||
projectId,
|
|
||||||
groupId,
|
|
||||||
roles,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
}: TUpdateProjectGroupDTO) => {
|
|
||||||
const project = await projectDAL.findById(projectId);
|
|
||||||
|
|
||||||
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
|
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Edit, ProjectPermissionSub.Groups);
|
|
||||||
|
|
||||||
const group = await groupDAL.findOne({ orgId: actorOrgId, id: groupId });
|
|
||||||
if (!group) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
|
|
||||||
|
|
||||||
const projectGroup = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
|
|
||||||
if (!projectGroup) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
|
|
||||||
|
|
||||||
for await (const { role: requestedRoleChange } of roles) {
|
|
||||||
const { permission: rolePermission } = await permissionService.getProjectPermissionByRole(
|
|
||||||
requestedRoleChange,
|
|
||||||
project.id
|
|
||||||
);
|
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionGroupActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Groups,
|
|
||||||
permission,
|
|
||||||
rolePermission
|
|
||||||
);
|
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new PermissionBoundaryError({
|
|
||||||
message: constructPermissionErrorMessage(
|
|
||||||
"Failed to assign group to role",
|
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
|
||||||
ProjectPermissionGroupActions.GrantPrivileges,
|
|
||||||
ProjectPermissionSub.Groups
|
|
||||||
),
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate custom roles input
|
|
||||||
const customInputRoles = roles.filter(
|
|
||||||
({ role }) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
|
|
||||||
);
|
|
||||||
const hasCustomRole = Boolean(customInputRoles.length);
|
|
||||||
const customRoles = hasCustomRole
|
|
||||||
? await projectRoleDAL.find({
|
|
||||||
projectId: project.id,
|
|
||||||
$in: { slug: customInputRoles.map(({ role }) => role) }
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
if (customRoles.length !== customInputRoles.length) {
|
|
||||||
const customRoleSlugs = customRoles.map((customRole) => customRole.slug);
|
|
||||||
const missingInputRoles = customInputRoles
|
|
||||||
.filter((inputRole) => !customRoleSlugs.includes(inputRole.role))
|
|
||||||
.map((role) => role.role);
|
|
||||||
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Custom role/s not found: ${missingInputRoles.join(", ")}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
|
||||||
|
|
||||||
const sanitizedProjectMembershipRoles = roles.map((inputRole) => {
|
|
||||||
const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]);
|
|
||||||
if (!inputRole.isTemporary) {
|
|
||||||
return {
|
|
||||||
projectMembershipId: projectGroup.id,
|
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
|
||||||
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// check cron or relative here later for now its just relative
|
|
||||||
const relativeTimeInMs = ms(inputRole.temporaryRange);
|
|
||||||
return {
|
|
||||||
projectMembershipId: projectGroup.id,
|
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
|
||||||
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null,
|
|
||||||
isTemporary: true,
|
|
||||||
temporaryMode: ProjectUserMembershipTemporaryMode.Relative,
|
|
||||||
temporaryRange: inputRole.temporaryRange,
|
|
||||||
temporaryAccessStartTime: new Date(inputRole.temporaryAccessStartTime),
|
|
||||||
temporaryAccessEndTime: new Date(new Date(inputRole.temporaryAccessStartTime).getTime() + relativeTimeInMs)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedRoles = await groupProjectMembershipRoleDAL.transaction(async (tx) => {
|
|
||||||
await groupProjectMembershipRoleDAL.delete({ projectMembershipId: projectGroup.id }, tx);
|
|
||||||
return groupProjectMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
|
||||||
|
|
||||||
return updatedRoles;
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeGroupFromProject = async ({
|
|
||||||
projectId,
|
|
||||||
groupId,
|
|
||||||
actorId,
|
|
||||||
actor,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod
|
|
||||||
}: TDeleteProjectGroupDTO) => {
|
|
||||||
const project = await projectDAL.findById(projectId);
|
|
||||||
|
|
||||||
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
|
|
||||||
|
|
||||||
const group = await groupDAL.findOne({ orgId: actorOrgId, id: groupId });
|
|
||||||
if (!group) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
|
|
||||||
|
|
||||||
const groupProjectMembership = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
|
|
||||||
if (!groupProjectMembership) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Delete, ProjectPermissionSub.Groups);
|
|
||||||
|
|
||||||
const deletedProjectGroup = await groupProjectDAL.transaction(async (tx) => {
|
|
||||||
const groupMembersNotInProject = await userGroupMembershipDAL.findGroupMembersNotInProject(
|
|
||||||
group.id,
|
|
||||||
project.id,
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
if (groupMembersNotInProject.length) {
|
|
||||||
await projectKeyDAL.delete(
|
|
||||||
{
|
|
||||||
projectId: project.id,
|
|
||||||
$in: {
|
|
||||||
receiverId: groupMembersNotInProject.map(({ user: { id } }) => id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [projectGroup] = await groupProjectDAL.delete({ groupId: group.id, projectId: project.id }, tx);
|
|
||||||
return projectGroup;
|
|
||||||
});
|
|
||||||
|
|
||||||
await permissionService.invalidateProjectPermissionCache(projectId);
|
|
||||||
|
|
||||||
return deletedProjectGroup;
|
|
||||||
};
|
|
||||||
|
|
||||||
const listGroupsInProject = async ({
|
|
||||||
projectId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
}: TListProjectGroupDTO) => {
|
|
||||||
const project = await projectDAL.findById(projectId);
|
|
||||||
|
|
||||||
if (!project) {
|
|
||||||
throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
|
|
||||||
|
|
||||||
const groupMemberships = await groupProjectDAL.findByProjectId(project.id);
|
|
||||||
return groupMemberships;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getGroupInProject = async ({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
groupId,
|
|
||||||
projectId
|
|
||||||
}: TGetGroupInProjectDTO) => {
|
|
||||||
const project = await projectDAL.findById(projectId);
|
|
||||||
|
|
||||||
if (!project) {
|
|
||||||
throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
|
|
||||||
|
|
||||||
const [groupMembership] = await groupProjectDAL.findByProjectId(project.id, {
|
|
||||||
groupId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!groupMembership) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Group membership with ID ${groupId} not found in project with ID ${projectId}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupMembership;
|
|
||||||
};
|
|
||||||
|
|
||||||
const listProjectGroupUsers = async ({
|
const listProjectGroupUsers = async ({
|
||||||
id,
|
id,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -542,11 +65,6 @@ export const groupProjectServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addGroupToProject,
|
|
||||||
updateGroupInProject,
|
|
||||||
removeGroupFromProject,
|
|
||||||
listGroupsInProject,
|
|
||||||
getGroupInProject,
|
|
||||||
listProjectGroupUsers
|
listProjectGroupUsers
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -217,8 +217,8 @@ export const membershipGroupServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
await membershipRoleDAL.insertMany(roleDocs, tx);
|
const roles = await membershipRoleDAL.insertMany(roleDocs, tx);
|
||||||
return doc;
|
return { ...doc, roles };
|
||||||
});
|
});
|
||||||
|
|
||||||
return { membership: membershipDoc };
|
return { membership: membershipDoc };
|
||||||
@@ -276,7 +276,7 @@ export const membershipGroupServiceFactory = ({
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return memberships;
|
return { memberships: memberships.data, totalCount: memberships.totalCount };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMembershipByGroupId = async (dto: TGetMembershipGroupByGroupIdDTO) => {
|
const getMembershipByGroupId = async (dto: TGetMembershipGroupByGroupIdDTO) => {
|
||||||
@@ -290,7 +290,7 @@ export const membershipGroupServiceFactory = ({
|
|||||||
});
|
});
|
||||||
if (!membership) throw new NotFoundError({ message: `Group membership not found` });
|
if (!membership) throw new NotFoundError({ message: `Group membership not found` });
|
||||||
|
|
||||||
return membership;
|
return { membership };
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -54,14 +54,11 @@ export type TUpdateMembershipGroupDTO = {
|
|||||||
export type TListMembershipGroupDTO = {
|
export type TListMembershipGroupDTO = {
|
||||||
permission: OrgServiceActor;
|
permission: OrgServiceActor;
|
||||||
scopeData: AccessScopeData;
|
scopeData: AccessScopeData;
|
||||||
selector: {
|
|
||||||
groupId: string;
|
|
||||||
};
|
|
||||||
data: {
|
data: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
groupName?: string;
|
groupName?: string;
|
||||||
roles: string[];
|
roles?: string[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type TProjectMembershipGroupScopeFactoryDep = {
|
|||||||
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "findOne">;
|
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "findOne">;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO(simp): check for group in org
|
||||||
export const newProjectMembershipGroupFactory = ({
|
export const newProjectMembershipGroupFactory = ({
|
||||||
permissionService,
|
permissionService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
|
import { AccessScope } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||||
|
|
||||||
|
import { TMembershipUserDALFactory } from "../membership-user/membership-user-dal";
|
||||||
|
|
||||||
type TDeleteOrgMembership = {
|
type TDeleteOrgMembership = {
|
||||||
orgMembershipId: string;
|
orgMembershipId: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "deleteMembershipById" | "transaction">;
|
orgDAL: Pick<TOrgDALFactory, "findMembership" | "deleteMembershipById" | "transaction">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "delete" | "findProjectMembershipsByUserId">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId">;
|
||||||
|
membershipUserDAL: Pick<TMembershipUserDALFactory, "delete">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
||||||
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
|
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
||||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
|
||||||
userId?: string;
|
userId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,11 +24,11 @@ type TDeleteOrgMemberships = {
|
|||||||
orgMembershipIds: string[];
|
orgMembershipIds: string[];
|
||||||
orgId: string;
|
orgId: string;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "deleteMembershipsById" | "transaction">;
|
orgDAL: Pick<TOrgDALFactory, "findMembership" | "deleteMembershipsById" | "transaction">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "delete" | "findProjectMembershipsByUserIds">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserIds">;
|
||||||
|
membershipUserDAL: Pick<TMembershipUserDALFactory, "delete">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
||||||
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
|
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
||||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
|
||||||
userId?: string;
|
userId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,46 +37,41 @@ export const deleteOrgMembershipFn = async ({
|
|||||||
orgId,
|
orgId,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectUserAdditionalPrivilegeDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
userId
|
userId,
|
||||||
|
membershipUserDAL
|
||||||
}: TDeleteOrgMembership) => {
|
}: TDeleteOrgMembership) => {
|
||||||
const deletedMembership = await orgDAL.transaction(async (tx) => {
|
const deletedMembership = await orgDAL.transaction(async (tx) => {
|
||||||
const orgMembership = await orgDAL.deleteMembershipById(orgMembershipId, orgId, tx);
|
const orgMembership = await orgDAL.deleteMembershipById(orgMembershipId, orgId, tx);
|
||||||
|
|
||||||
if (userId && orgMembership.userId === userId) {
|
if (userId && orgMembership.actorUserId === userId) {
|
||||||
// scott: this is temporary, we will add a leave org endpoint with proper handling to ensure org isn't abandoned/broken
|
// scott: this is temporary, we will add a leave org endpoint with proper handling to ensure org isn't abandoned/broken
|
||||||
throw new BadRequestError({ message: "You cannot remove yourself from an organization" });
|
throw new BadRequestError({ message: "You cannot remove yourself from an organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!orgMembership.userId) {
|
const deletedUserId = orgMembership.actorUserId;
|
||||||
|
if (!deletedUserId) {
|
||||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||||
return orgMembership;
|
return orgMembership;
|
||||||
}
|
}
|
||||||
|
|
||||||
await userAliasDAL.delete(
|
await userAliasDAL.delete(
|
||||||
{
|
{
|
||||||
userId: orgMembership.userId,
|
userId: deletedUserId,
|
||||||
orgId
|
orgId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectUserAdditionalPrivilegeDAL.delete(
|
|
||||||
{
|
|
||||||
userId: orgMembership.userId
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get all the project memberships of the user in the organization
|
// Get all the project memberships of the user in the organization
|
||||||
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, orgMembership.userId);
|
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, deletedUserId);
|
||||||
|
|
||||||
// Delete all the project memberships of the user in the organization
|
// Delete all the project memberships of the user in the organization
|
||||||
await projectMembershipDAL.delete(
|
await membershipUserDAL.delete(
|
||||||
{
|
{
|
||||||
|
scope: AccessScope.Project,
|
||||||
$in: {
|
$in: {
|
||||||
id: projectMemberships.map((membership) => membership.id)
|
id: projectMemberships.map((membership) => membership.id)
|
||||||
}
|
}
|
||||||
@@ -87,7 +84,7 @@ export const deleteOrgMembershipFn = async ({
|
|||||||
$in: {
|
$in: {
|
||||||
projectId: projectMemberships.map((membership) => membership.projectId)
|
projectId: projectMemberships.map((membership) => membership.projectId)
|
||||||
},
|
},
|
||||||
receiverId: orgMembership.userId
|
receiverId: deletedUserId
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete all the project keys of the user in the organization
|
// Delete all the project keys of the user in the organization
|
||||||
@@ -112,18 +109,18 @@ export const deleteOrgMembershipsFn = async ({
|
|||||||
orgId,
|
orgId,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectUserAdditionalPrivilegeDAL,
|
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
userId
|
userId,
|
||||||
|
membershipUserDAL
|
||||||
}: TDeleteOrgMemberships) => {
|
}: TDeleteOrgMemberships) => {
|
||||||
const deletedMemberships = await orgDAL.transaction(async (tx) => {
|
const deletedMemberships = await orgDAL.transaction(async (tx) => {
|
||||||
const orgMemberships = await orgDAL.deleteMembershipsById(orgMembershipIds, orgId, tx);
|
const orgMemberships = await orgDAL.deleteMembershipsById(orgMembershipIds, orgId, tx);
|
||||||
|
|
||||||
const membershipUserIds = orgMemberships
|
const membershipUserIds = orgMemberships
|
||||||
.filter((member) => Boolean(member.userId))
|
.filter((member) => Boolean(member.actorUserId))
|
||||||
.map((member) => member.userId) as string[];
|
.map((member) => member.actorUserId) as string[];
|
||||||
|
|
||||||
if (userId && membershipUserIds.includes(userId)) {
|
if (userId && membershipUserIds.includes(userId)) {
|
||||||
// scott: this is temporary, we will add a leave org endpoint with proper handling to ensure org isn't abandoned/broken
|
// scott: this is temporary, we will add a leave org endpoint with proper handling to ensure org isn't abandoned/broken
|
||||||
@@ -145,21 +142,13 @@ export const deleteOrgMembershipsFn = async ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectUserAdditionalPrivilegeDAL.delete(
|
|
||||||
{
|
|
||||||
$in: {
|
|
||||||
userId: membershipUserIds
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get all the project memberships of the users in the organization
|
// Get all the project memberships of the users in the organization
|
||||||
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserIds(orgId, membershipUserIds);
|
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserIds(orgId, membershipUserIds);
|
||||||
|
|
||||||
// Delete all the project memberships of the users in the organization
|
// Delete all the project memberships of the users in the organization
|
||||||
await projectMembershipDAL.delete(
|
await membershipUserDAL.delete(
|
||||||
{
|
{
|
||||||
|
scope: AccessScope.Project,
|
||||||
$in: {
|
$in: {
|
||||||
id: projectMemberships.map((membership) => membership.id)
|
id: projectMemberships.map((membership) => membership.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import { TDbClient } from "@app/db";
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { ormify } from "@app/lib/knex";
|
|
||||||
|
|
||||||
export type TProjectUserMembershipRoleDALFactory = ReturnType<typeof projectUserMembershipRoleDALFactory>;
|
|
||||||
|
|
||||||
export const projectUserMembershipRoleDALFactory = (db: TDbClient) => {
|
|
||||||
const orm = ormify(db, TableName.ProjectUserMembershipRole);
|
|
||||||
return orm;
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user