From 44c9575a3da9193882aee82b9135ffc08c0ffb0e Mon Sep 17 00:00:00 2001 From: = Date: Thu, 2 Oct 2025 22:55:12 +0530 Subject: [PATCH] feat: battle of ts - unlimited edition --- .../20250925145952_simplify-membership.ts | 38 +- .../src/db/schemas/additional-privileges.ts | 6 +- backend/src/db/schemas/memberships.ts | 2 +- .../db/schemas/user-notifications-default.ts | 27 ++ ...ity-project-additional-privilege-router.ts | 60 ++- .../v1/user-additional-privilege-router.ts | 63 ++-- ...ity-project-additional-privilege-router.ts | 70 ++-- .../access-approval-policy-service.ts | 23 +- .../access-approval-request-service.ts | 7 +- .../ee/services/gateway-v2/gateway-v2-dal.ts | 7 +- .../src/ee/services/gateway/gateway-dal.ts | 17 +- backend/src/ee/services/group/group-dal.ts | 19 +- .../ee/services/permission/permission-dal.ts | 342 +++++++----------- backend/src/server/routes/index.ts | 7 +- .../deprecated-project-membership-router.ts | 48 ++- .../src/server/routes/v1/org-admin-router.ts | 2 +- .../additional-privilege-service.ts | 56 ++- .../additional-privilege-types.ts | 26 +- .../project-additional-privilege-factory.ts | 29 +- .../services/convertor/convertor-service.ts | 18 +- .../services/org-admin/org-admin-service.ts | 3 +- backend/src/services/org/org-dal.ts | 2 +- backend/src/services/role/role-service.ts | 4 +- .../super-admin/super-admin-service.ts | 9 +- .../OrgPermissionContext.tsx | 15 +- .../ProjectPermissionContext.tsx | 12 +- frontend/src/hooks/api/roles/queries.tsx | 16 +- frontend/src/hooks/api/users/types.ts | 7 + .../UpgradePrivilegeSystemModal.tsx | 7 +- .../ExternalMigrationsTab.tsx | 6 +- .../OrgDeleteSection/OrgDeleteSection.tsx | 5 +- .../OrgGeneralTab/OrgGeneralTab.tsx | 5 +- .../AuditLogsRetentionSection.tsx | 4 +- .../DeleteProjectSection.tsx | 12 +- .../SecretV2MigrationSection.tsx | 4 +- .../BackfillSecretReferenceSection.tsx | 4 +- .../PointInTimeVersionLimitSection.tsx | 4 +- .../SecretDetectionIgnoreValuesSection.tsx | 4 +- 38 files changed, 492 insertions(+), 498 deletions(-) create mode 100644 backend/src/db/schemas/user-notifications-default.ts diff --git a/backend/src/db/migrations/20250925145952_simplify-membership.ts b/backend/src/db/migrations/20250925145952_simplify-membership.ts index a5d8df844..4e4219538 100644 --- a/backend/src/db/migrations/20250925145952_simplify-membership.ts +++ b/backend/src/db/migrations/20250925145952_simplify-membership.ts @@ -37,7 +37,7 @@ export async function up(knex: Knex): Promise { t.foreign("scopeNamespaceId").references("id").inTable(TableName.Namespace).onDelete("CASCADE"); t.boolean("isActive").defaultTo(true).notNullable(); - t.string("status").defaultTo("invited"); + t.string("status").defaultTo("invited").notNullable(); t.string("inviteEmail"); t.datetime("lastInvitedAt"); t.string("lastLoginAuthMethod"); @@ -139,8 +139,40 @@ export async function up(knex: Knex): Promise { t.datetime("temporaryAccessEndTime"); t.jsonb("permissions").notNullable(); - t.uuid("membershipId").notNullable(); - t.foreign("membershipId").references("id").inTable(TableName.Membership).onDelete("CASCADE"); + t.uuid("actorUserId"); + t.foreign("actorUserId").references("id").inTable(TableName.Users).onDelete("CASCADE"); + t.uuid("actorIdentityId"); + t.foreign("actorIdentityId").references("id").inTable(TableName.Identity).onDelete("CASCADE"); + + t.uuid("orgId"); + t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); + t.string("projectId", 36); + t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); + t.uuid("namespaceId"); + t.foreign("namespaceId").references("id").inTable(TableName.Namespace).onDelete("CASCADE"); + + t.check( + `(:orgIdColumn: IS NOT NULL AND :namespaceIdColumn: IS NULL AND :projectIdColumn: IS NULL) OR + (:namespaceIdColumn: IS NOT NULL AND :orgIdColumn: IS NULL AND :projectIdColumn: IS NULL) OR + (:projectIdColumn: IS NOT NULL AND :orgIdColumn: IS NULL AND :namespaceIdColumn: IS NULL)`, + { + orgIdColumn: "orgId", + namespaceIdColumn: "namespaceId", + projectIdColumn: "projectId" + }, + "only_one_scope_id" + ); + + t.check( + `(:actorUserIdColumn: IS NOT NULL AND :actorIdentityIdColumn: IS NULL) OR + (:actorIdentityIdColumn: IS NOT NULL AND :actorUserIdColumn: IS NULL) + `, + { + actorUserIdColumn: "actorUserId", + actorIdentityIdColumn: "actorIdentityId" + }, + "only_one_actor_type" + ); t.timestamps(true, true, true); }); diff --git a/backend/src/db/schemas/additional-privileges.ts b/backend/src/db/schemas/additional-privileges.ts index 887148554..8013a6961 100644 --- a/backend/src/db/schemas/additional-privileges.ts +++ b/backend/src/db/schemas/additional-privileges.ts @@ -16,7 +16,11 @@ export const AdditionalPrivilegesSchema = z.object({ temporaryAccessStartTime: z.date().nullable().optional(), temporaryAccessEndTime: z.date().nullable().optional(), permissions: z.unknown(), - membershipId: z.string().uuid(), + actorUserId: z.string().uuid().nullable().optional(), + actorIdentityId: z.string().uuid().nullable().optional(), + orgId: z.string().uuid().nullable().optional(), + projectId: z.string().nullable().optional(), + namespaceId: z.string().uuid().nullable().optional(), createdAt: z.date(), updatedAt: z.date() }); diff --git a/backend/src/db/schemas/memberships.ts b/backend/src/db/schemas/memberships.ts index 55115091b..342880394 100644 --- a/backend/src/db/schemas/memberships.ts +++ b/backend/src/db/schemas/memberships.ts @@ -17,7 +17,7 @@ export const MembershipsSchema = z.object({ scopeProjectId: z.string().nullable().optional(), scopeNamespaceId: z.string().uuid().nullable().optional(), isActive: z.boolean().default(true), - status: z.string().default("invited").nullable().optional(), + status: z.string().default("invited"), inviteEmail: z.string().nullable().optional(), lastInvitedAt: z.date().nullable().optional(), lastLoginAuthMethod: z.string().nullable().optional(), diff --git a/backend/src/db/schemas/user-notifications-default.ts b/backend/src/db/schemas/user-notifications-default.ts new file mode 100644 index 000000000..eabcdabc5 --- /dev/null +++ b/backend/src/db/schemas/user-notifications-default.ts @@ -0,0 +1,27 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const UserNotificationsDefaultSchema = z.object({ + id: z.string().uuid(), + userId: z.string().uuid(), + orgId: z.string().uuid().nullable().optional(), + type: z.string(), + title: z.string(), + body: z.string().nullable().optional(), + link: z.string().nullable().optional(), + isRead: z.boolean().default(false), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TUserNotificationsDefault = z.infer; +export type TUserNotificationsDefaultInsert = Omit, TImmutableDBKeys>; +export type TUserNotificationsDefaultUpdate = Partial< + Omit, TImmutableDBKeys> +>; diff --git a/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts b/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts index 37deb878f..9a74b3b72 100644 --- a/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts +++ b/backend/src/ee/routes/v1/identity-project-additional-privilege-router.ts @@ -1,6 +1,7 @@ import slugify from "@sindresorhus/slugify"; import { z } from "zod"; +import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas"; import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission"; import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs"; import { UnauthorizedError } from "@app/lib/errors"; @@ -14,8 +15,7 @@ import { ProjectSpecificPrivilegePermissionSchema, SanitizedIdentityPrivilegeSchema } from "@app/server/routes/sanitizedSchemas"; -import { AuthMode } from "@app/services/auth/auth-type"; -import { AccessScope, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas"; +import { ActorType, AuthMode } from "@app/services/auth/auth-type"; export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { server.route({ @@ -78,7 +78,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, data: { actorId: req.body.identityId, - actorType: MembershipActors.Identity, + actorType: ActorType.IDENTITY, ...req.body, isTemporary: false, name: req.body.slug || slugify(alphaNumericNanoId(8)), @@ -92,7 +92,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F privilege: { ...privilege, identityId: req.body.identityId, - projectMembershipId: privilege.membershipId, + projectMembershipId: "", projectId, slug: privilege.name } @@ -172,7 +172,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, data: { actorId: req.body.identityId, - actorType: MembershipActors.Identity, + actorType: ActorType.IDENTITY, ...req.body, isTemporary: true, name: req.body.slug || slugify(alphaNumericNanoId(8)), @@ -186,7 +186,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F privilege: { ...privilege, identityId: req.body.identityId, - projectMembershipId: privilege.membershipId, + projectMembershipId: "", projectId, slug: privilege.name } @@ -262,15 +262,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F slug: req.body.projectSlug }); - const { membershipId } = await server.services.convertor.identityIdToMembershipId( - req.body.identityId, - AccessScope.Project, - projectId - ); - - const { privilegeId } = await server.services.convertor.additionalPrivilegeNameToId( + const { privilege: privilegeDoc } = await server.services.convertor.additionalPrivilegeNameToDoc( req.body.privilegeSlug, - membershipId + projectId ); const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.updateAdditionalPrivilege({ @@ -282,8 +276,8 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, selector: { actorId: req.body.identityId, - actorType: MembershipActors.Identity, - id: privilegeId + actorType: ActorType.IDENTITY, + id: privilegeDoc.id }, data: { ...req.body, @@ -305,7 +299,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F privilege: { ...privilege, identityId: req.body.identityId, - projectMembershipId: privilege.membershipId, + projectMembershipId: "", projectId, slug: privilege.name } @@ -346,15 +340,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F slug: req.body.projectSlug }); - const { membershipId } = await server.services.convertor.identityIdToMembershipId( - req.body.identityId, - AccessScope.Project, - projectId - ); - - const { privilegeId } = await server.services.convertor.additionalPrivilegeNameToId( + const { privilegeId } = await server.services.convertor.additionalPrivilegeNameToDoc( req.body.privilegeSlug, - membershipId + projectId ); const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.deleteAdditionalPrivilege({ @@ -366,7 +354,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, selector: { actorId: req.body.identityId, - actorType: MembershipActors.Identity, + actorType: ActorType.IDENTITY, id: privilegeId } }); @@ -375,7 +363,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F privilege: { ...privilege, identityId: req.body.identityId, - projectMembershipId: privilege.membershipId, + projectMembershipId: "", projectId, slug: privilege.name } @@ -418,15 +406,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F slug: req.query.projectSlug }); - const { membershipId } = await server.services.convertor.identityIdToMembershipId( - req.query.identityId, - AccessScope.Project, - projectId - ); - - const { privilegeId } = await server.services.convertor.additionalPrivilegeNameToId( + const { privilegeId } = await server.services.convertor.additionalPrivilegeNameToDoc( req.params.privilegeSlug, - membershipId + projectId ); const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeById({ @@ -438,7 +420,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, selector: { actorId: req.query.identityId, - actorType: MembershipActors.Identity, + actorType: ActorType.IDENTITY, id: privilegeId } }); @@ -447,7 +429,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F privilege: { ...privilege, identityId: req.query.identityId, - projectMembershipId: privilege.membershipId, + projectMembershipId: "", projectId, slug: privilege.name } @@ -496,7 +478,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, selector: { actorId: req.query.identityId, - actorType: MembershipActors.Identity + actorType: ActorType.IDENTITY } }); @@ -504,7 +486,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F privileges: privileges.map((privilege) => ({ ...privilege, identityId: req.query.identityId, - projectMembershipId: privilege.membershipId, + projectMembershipId: "", projectId, slug: privilege.name })) diff --git a/backend/src/ee/routes/v1/user-additional-privilege-router.ts b/backend/src/ee/routes/v1/user-additional-privilege-router.ts index fa4d9a334..926b22231 100644 --- a/backend/src/ee/routes/v1/user-additional-privilege-router.ts +++ b/backend/src/ee/routes/v1/user-additional-privilege-router.ts @@ -1,17 +1,18 @@ import slugify from "@sindresorhus/slugify"; import { z } from "zod"; -import { AccessScope, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas"; +import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas"; import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs"; +import { NotFoundError } from "@app/lib/errors"; import { ms } from "@app/lib/ms"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/sanitizedSchema/user-additional-privilege"; -import { AuthMode } from "@app/services/auth/auth-type"; +import { ActorType, AuthMode } from "@app/services/auth/auth-type"; export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { server.route({ @@ -70,7 +71,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }, data: { actorId: userId, - actorType: MembershipActors.User, + actorType: ActorType.USER, ...req.body.type, name: req.body.slug || slugify(alphaNumericNanoId(8)), permissions: req.body.permissions @@ -127,17 +128,15 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId( - req.params.privilegeId, - AccessScope.Project, - req.permission.orgId - ); + const data = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.privilegeId); + if (!data.privilege.actorUserId) + throw new NotFoundError({ message: `Privilege with id ${req.params.privilegeId} not found` }); const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.updateAdditionalPrivilege({ permission: req.permission, scopeData: { scope: AccessScope.Project, - projectId: membership.scopeProjectId as string, + projectId: data.privilege.projectId as string, orgId: req.permission.orgId }, data: { @@ -151,16 +150,16 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }, selector: { id: req.params.privilegeId, - actorId: membership.actorUserId as string, - actorType: MembershipActors.User + actorId: data.privilege.actorUserId, + actorType: ActorType.USER } }); return { privilege: { ...privilege, - userId: membership.actorUserId as string, - projectId: membership.scopeProjectId as string, + userId: data.privilege.actorUserId, + projectId: data.privilege.projectId as string, slug: privilege.name } }; @@ -185,31 +184,29 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId( - req.params.privilegeId, - AccessScope.Project, - req.permission.orgId - ); + const data = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.privilegeId); + if (!data.privilege.actorUserId) + throw new NotFoundError({ message: `Privilege with id ${req.params.privilegeId} not found` }); const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.deleteAdditionalPrivilege({ permission: req.permission, scopeData: { scope: AccessScope.Project, - projectId: membership.scopeProjectId as string, + projectId: data.privilege.projectId as string, orgId: req.permission.orgId }, selector: { id: req.params.privilegeId, - actorId: membership.actorUserId as string, - actorType: MembershipActors.User + actorId: data.privilege.actorUserId, + actorType: ActorType.USER } }); return { privilege: { ...privilege, - userId: membership.actorUserId as string, - projectId: membership.scopeProjectId as string, + userId: data.privilege.actorUserId, + projectId: data.privilege.projectId as string, slug: privilege.name } }; @@ -249,7 +246,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }, selector: { actorId: userId, - actorType: MembershipActors.User + actorType: ActorType.USER } }); @@ -282,31 +279,29 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId( - req.params.privilegeId, - AccessScope.Project, - req.permission.orgId - ); + const data = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.privilegeId); + if (!data.privilege.actorUserId) + throw new NotFoundError({ message: `Privilege with id ${req.params.privilegeId} not found` }); const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeById({ permission: req.permission, scopeData: { scope: AccessScope.Project, - projectId: membership.scopeProjectId as string, + projectId: data.privilege.projectId as string, orgId: req.permission.orgId }, selector: { id: req.params.privilegeId, - actorId: membership.actorUserId as string, - actorType: MembershipActors.User + actorId: data.privilege.actorUserId, + actorType: ActorType.USER } }); return { privilege: { ...privilege, - userId: membership.actorUserId as string, - projectId: membership.scopeProjectId as string, + userId: data.privilege.actorUserId, + projectId: data.privilege.projectId as string, slug: privilege.name } }; diff --git a/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts b/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts index 89d8529f9..3d113e649 100644 --- a/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts +++ b/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts @@ -1,7 +1,7 @@ import slugify from "@sindresorhus/slugify"; import { z } from "zod"; -import { AccessScope, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas"; +import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas"; import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { ApiDocsTags, IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs"; @@ -11,7 +11,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/sanitizedSchema/identitiy-additional-privilege"; -import { AuthMode } from "@app/services/auth/auth-type"; +import { ActorType, AuthMode } from "@app/services/auth/auth-type"; export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { server.route({ @@ -73,7 +73,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, data: { actorId: req.body.identityId, - actorType: MembershipActors.Identity, + actorType: ActorType.IDENTITY, ...req.body.type, name: req.body.slug || slugify(alphaNumericNanoId(8)), permissions: req.body.permissions @@ -84,7 +84,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F privilege: { ...privilege, identityId: req.body.identityId, - projectMembershipId: privilege.membershipId, + projectMembershipId: "", projectId: req.body.projectId, slug: privilege.name } @@ -142,23 +142,19 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId( - req.params.id, - AccessScope.Project, - req.permission.orgId - ); + const { privilege: privilegeDoc } = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.id); const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.updateAdditionalPrivilege({ permission: req.permission, scopeData: { scope: AccessScope.Project, - projectId: membership.scopeProjectId as string, + projectId: privilegeDoc.projectId as string, orgId: req.permission.orgId }, selector: { id: req.params.id, - actorId: membership.actorIdentityId as string, - actorType: MembershipActors.Identity + actorId: privilegeDoc.actorIdentityId as string, + actorType: ActorType.IDENTITY }, data: { ...req.body, @@ -170,9 +166,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F return { privilege: { ...privilege, - identityId: membership.actorIdentityId as string, - projectMembershipId: privilege.membershipId, - projectId: membership.scopeProjectId as string, + identityId: privilegeDoc.actorIdentityId as string, + projectMembershipId: "", + projectId: privilegeDoc.projectId as string, slug: privilege.name } }; @@ -205,32 +201,28 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId( - req.params.id, - AccessScope.Project, - req.permission.orgId - ); + const { privilege: privilegeDoc } = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.id); const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.deleteAdditionalPrivilege({ permission: req.permission, scopeData: { scope: AccessScope.Project, - projectId: membership.scopeProjectId as string, + projectId: privilegeDoc.projectId as string, orgId: req.permission.orgId }, selector: { id: req.params.id, - actorId: membership.actorIdentityId as string, - actorType: MembershipActors.Identity + actorId: privilegeDoc.actorIdentityId as string, + actorType: ActorType.IDENTITY } }); return { privilege: { ...privilege, - identityId: membership.actorIdentityId as string, - projectMembershipId: privilege.membershipId, - projectId: membership.scopeProjectId as string, + identityId: privilegeDoc.actorIdentityId as string, + projectMembershipId: "", + projectId: privilegeDoc.projectId as string, slug: privilege.name } }; @@ -263,32 +255,28 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { membership } = await server.services.convertor.additionalPrivilegeIdToMembershipId( - req.params.id, - AccessScope.Project, - req.permission.orgId - ); + const { privilege: privilegeDoc } = await server.services.convertor.additionalPrivilegeIdToDoc(req.params.id); const { additionalPrivilege: privilege } = await server.services.additionalPrivilege.getAdditionalPrivilegeById({ permission: req.permission, scopeData: { scope: AccessScope.Project, - projectId: membership.scopeProjectId as string, + projectId: privilegeDoc.projectId as string, orgId: req.permission.orgId }, selector: { id: req.params.id, - actorId: membership.actorIdentityId as string, - actorType: MembershipActors.Identity + actorId: privilegeDoc.actorIdentityId as string, + actorType: ActorType.IDENTITY } }); return { privilege: { ...privilege, - identityId: membership.actorIdentityId as string, - projectMembershipId: privilege.membershipId, - projectId: membership.scopeProjectId as string, + identityId: privilegeDoc.actorIdentityId as string, + projectMembershipId: "", + projectId: privilegeDoc.projectId as string, slug: privilege.name } }; @@ -341,7 +329,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F selector: { name: req.params.privilegeSlug, actorId: req.query.identityId, - actorType: MembershipActors.Identity + actorType: ActorType.IDENTITY } } ); @@ -350,7 +338,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F privilege: { ...privilege, identityId: req.query.identityId, - projectMembershipId: privilege.membershipId, + projectMembershipId: "", projectId, slug: privilege.name } @@ -394,7 +382,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F }, selector: { actorId: req.query.identityId, - actorType: MembershipActors.Identity + actorType: ActorType.IDENTITY } }); @@ -402,7 +390,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F privileges: privileges.map((privilege) => ({ ...privilege, identityId: req.query.identityId, - projectMembershipId: privilege.membershipId, + projectMembershipId: "", projectId: req.query.projectId, slug: privilege.name })) diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts index 929fdd575..31e28259f 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts @@ -1,21 +1,18 @@ import { ForbiddenError } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; +import { AccessScope, ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; -import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; -import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { TUserDALFactory } from "@app/services/user/user-dal"; import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal"; import { TAccessApprovalRequestReviewerDALFactory } from "../access-approval-request/access-approval-request-reviewer-dal"; import { ApprovalStatus } from "../access-approval-request/access-approval-request-types"; import { TGroupDALFactory } from "../group/group-dal"; -import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal"; import { TAccessApprovalPolicyApproverDALFactory, TAccessApprovalPolicyBypasserDALFactory @@ -31,6 +28,8 @@ import { TListAccessApprovalPoliciesDTO, TUpdateAccessApprovalPolicy } from "./access-approval-policy-types"; +import { TMembershipUserDALFactory } from "@app/services/membership-user/membership-user-dal"; +import { TAdditionalPrivilegeDALFactory } from "@app/services/additional-privilege/additional-privilege-dal"; type TAccessApprovalPolicyServiceFactoryDep = { projectDAL: TProjectDALFactory; @@ -39,14 +38,13 @@ type TAccessApprovalPolicyServiceFactoryDep = { projectEnvDAL: Pick; accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory; accessApprovalPolicyBypasserDAL: TAccessApprovalPolicyBypasserDALFactory; - projectMembershipDAL: Pick; groupDAL: TGroupDALFactory; userDAL: Pick; accessApprovalRequestDAL: Pick; - additionalPrivilegeDAL: Pick; + additionalPrivilegeDAL: Pick; accessApprovalRequestReviewerDAL: Pick; - orgMembershipDAL: Pick; accessApprovalPolicyEnvironmentDAL: TAccessApprovalPolicyEnvironmentDALFactory; + membershipUserDAL: TMembershipUserDALFactory; }; export const accessApprovalPolicyServiceFactory = ({ @@ -62,7 +60,7 @@ export const accessApprovalPolicyServiceFactory = ({ accessApprovalRequestDAL, additionalPrivilegeDAL, accessApprovalRequestReviewerDAL, - orgMembershipDAL + membershipUserDAL }: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => { const $policyExists = async ({ envId, @@ -424,13 +422,14 @@ export const accessApprovalPolicyServiceFactory = ({ // Validate user bypassers if (bypasserUserIds.length > 0) { - const orgMemberships = await orgMembershipDAL.find({ - $in: { userId: bypasserUserIds }, - orgId: actorOrgId + const orgMemberships = await membershipUserDAL.find({ + $in: { actorUserId: bypasserUserIds }, + scopeOrgId: actorOrgId, + scope: AccessScope.Organization }); if (orgMemberships.length !== bypasserUserIds.length) { - const foundUserIdsInOrg = new Set(orgMemberships.map((mem) => mem.userId)); + const foundUserIdsInOrg = new Set(orgMemberships.map((mem) => mem.actorUserId as string)); const missingUserIds = bypasserUserIds.filter((id) => !foundUserIdsInOrg.has(id)); throw new BadRequestError({ message: `One or more specified bypasser users are not part of the organization or do not exist. Invalid or non-member user IDs: ${missingUserIds.join(", ")}` diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts index 551321fd3..4e1f7d1f8 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts @@ -26,16 +26,15 @@ import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-poli import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal"; import { TGroupDALFactory } from "../group/group-dal"; import { TPermissionServiceFactory } from "../permission/permission-service-types"; -import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal"; -import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types"; import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal"; import { verifyRequestedPermissions } from "./access-approval-request-fns"; import { TAccessApprovalRequestReviewerDALFactory } from "./access-approval-request-reviewer-dal"; import { ApprovalStatus, TAccessApprovalRequestServiceFactory } from "./access-approval-request-types"; +import { TAdditionalPrivilegeDALFactory } from "@app/services/additional-privilege/additional-privilege-dal"; type TSecretApprovalRequestServiceFactoryDep = { - additionalPrivilegeDAL: Pick; - permissionService: Pick; + additionalPrivilegeDAL: Pick; + permissionService: Pick; accessApprovalPolicyApproverDAL: Pick; projectEnvDAL: Pick; projectDAL: Pick< diff --git a/backend/src/ee/services/gateway-v2/gateway-v2-dal.ts b/backend/src/ee/services/gateway-v2/gateway-v2-dal.ts index da9d3c1ef..e2099e803 100644 --- a/backend/src/ee/services/gateway-v2/gateway-v2-dal.ts +++ b/backend/src/ee/services/gateway-v2/gateway-v2-dal.ts @@ -1,7 +1,7 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { GatewaysV2Schema, TableName, TGatewaysV2 } from "@app/db/schemas"; +import { AccessScope, GatewaysV2Schema, TableName, TGatewaysV2 } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex"; @@ -16,11 +16,6 @@ export const gatewayV2DalFactory = (db: TDbClient) => { // eslint-disable-next-line @typescript-eslint/no-misused-promises .where(buildFindFilter(filter, TableName.GatewayV2)) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.GatewayV2}.identityId`) - .join( - TableName.IdentityOrgMembership, - `${TableName.IdentityOrgMembership}.identityId`, - `${TableName.GatewayV2}.identityId` - ) .select(selectAllTableCols(TableName.GatewayV2)) .select(db.ref("name").withSchema(TableName.Identity).as("identityName")); diff --git a/backend/src/ee/services/gateway/gateway-dal.ts b/backend/src/ee/services/gateway/gateway-dal.ts index c21ff31c0..b23d42d39 100644 --- a/backend/src/ee/services/gateway/gateway-dal.ts +++ b/backend/src/ee/services/gateway/gateway-dal.ts @@ -1,5 +1,5 @@ import { TDbClient } from "@app/db"; -import { GatewaysSchema, TableName, TGateways } from "@app/db/schemas"; +import { AccessScope, GatewaysSchema, TableName, TGateways } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex"; @@ -17,17 +17,14 @@ export const gatewayDALFactory = (db: TDbClient) => { // eslint-disable-next-line @typescript-eslint/no-misused-promises .where(buildFindFilter(filter, TableName.Gateway, ["orgId"])) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`) - .join( - TableName.IdentityOrgMembership, - `${TableName.IdentityOrgMembership}.identityId`, - `${TableName.Gateway}.identityId` - ) + .join(TableName.Membership, `${TableName.Membership}.actorIdentityId`, `${TableName.Gateway}.identityId`) .select(selectAllTableCols(TableName.Gateway)) - .select(db.ref("orgId").withSchema(TableName.IdentityOrgMembership).as("identityOrgId")) - .select(db.ref("name").withSchema(TableName.Identity).as("identityName")); + .select(db.ref("scopeOrgId").withSchema(TableName.IdentityOrgMembership).as("identityOrgId")) + .select(db.ref("name").withSchema(TableName.Identity).as("identityName")) + .where(`${TableName.Membership}.scope`, AccessScope.Organization); if (filter.orgId) { - void query.where(`${TableName.IdentityOrgMembership}.orgId`, filter.orgId); + void query.where(`${TableName.Membership}.scopeOrgId`, filter.orgId); } if (limit) void query.limit(limit); if (offset) void query.offset(offset); @@ -39,7 +36,7 @@ export const gatewayDALFactory = (db: TDbClient) => { return docs.map((el) => ({ ...GatewaysSchema.parse(el), - orgId: el.identityOrgId as string, // todo(daniel): figure out why typescript is not inferring this as a string + orgId: el.identityOrgId, identity: { id: el.identityId, name: el.identityName } })); } catch (error) { diff --git a/backend/src/ee/services/group/group-dal.ts b/backend/src/ee/services/group/group-dal.ts index 6e00e292b..b40bcac03 100644 --- a/backend/src/ee/services/group/group-dal.ts +++ b/backend/src/ee/services/group/group-dal.ts @@ -36,14 +36,21 @@ export const groupDALFactory = (db: TDbClient) => { try { const docs = await (tx || db.replicaNode())(TableName.Groups) .where(`${TableName.Groups}.orgId`, orgId) - .leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`) + .where(`${TableName.Membership}.scopeOrgId`, orgId) + .where(`${TableName.Membership}.scope`, AccessScope.Organization) + .join(TableName.Membership, `${TableName.Groups}.id`, `${TableName.Membership}.actorGroupId`) + .join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`) + .join(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`) .select(selectAllTableCols(TableName.Groups)) // cr stands for custom role - .select(db.ref("id").as("crId").withSchema(TableName.OrgRoles)) - .select(db.ref("name").as("crName").withSchema(TableName.OrgRoles)) - .select(db.ref("slug").as("crSlug").withSchema(TableName.OrgRoles)) - .select(db.ref("description").as("crDescription").withSchema(TableName.OrgRoles)) - .select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles)); + .select(db.ref("id").as("crId").withSchema(TableName.Role)) + .select(db.ref("name").as("crName").withSchema(TableName.Role)) + .select(db.ref("role").withSchema(TableName.MembershipRole)) + .select(db.ref("customRoleId").as("roleId").withSchema(TableName.MembershipRole)) + .select(db.ref("slug").as("crSlug").withSchema(TableName.Role)) + .select(db.ref("description").as("crDescription").withSchema(TableName.Role)) + .select(db.ref("permissions").as("crPermission").withSchema(TableName.Role)); + return docs.map(({ crId, crDescription, crSlug, crPermission, crName, ...el }) => ({ ...el, customRole: el.roleId diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index 7fb538e1f..7b81f592d 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -9,8 +9,7 @@ import { MembershipsSchema, TableName, TMemberships, - TProjectRoles, - TProjects + TProjectRoles } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; @@ -204,11 +203,19 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { .replicaNode()(TableName.Membership) .join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`) .leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`) - .leftJoin( - TableName.AdditionalPrivilege, - `${TableName.Membership}.id`, - `${TableName.AdditionalPrivilege}.membershipId` - ) + .leftJoin(TableName.AdditionalPrivilege, (qb) => { + if (actorType === ActorType.IDENTITY) { + qb.on(`${TableName.Membership}.actorIdentityId`, `${TableName.AdditionalPrivilege}.actorIdentityId`).andOn( + `${TableName.Membership}.scopeOrgId`, + `${TableName.AdditionalPrivilege}.orgId` + ); + } else { + qb.on(`${TableName.Membership}.actorUserId`, `${TableName.AdditionalPrivilege}.actorUserId`).andOn( + `${TableName.Membership}.scopeOrgId`, + `${TableName.AdditionalPrivilege}.orgId` + ); + } + }) .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { if (actorType === ActorType.USER) { void queryBuilder @@ -366,22 +373,20 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { ) => { try { const docs = await db - .replicaNode()(TableName.GroupProjectMembership) - .join(TableName.Groups, `${TableName.Groups}.id`, `${TableName.GroupProjectMembership}.groupId`) - .join( - TableName.GroupProjectMembershipRole, - `${TableName.GroupProjectMembershipRole}.projectMembershipId`, - `${TableName.GroupProjectMembership}.id` - ) + .replicaNode()(TableName.Membership) + .whereNotNull(`${TableName.Membership}.actorGroupId`) + .where(`${TableName.Membership}.scope`, AccessScope.Project) + .join(TableName.Groups, `${TableName.Groups}.id`, `${TableName.Membership}.actorGroupId`) + .join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`) .leftJoin( - { groupCustomRoles: TableName.ProjectRoles }, - `${TableName.GroupProjectMembershipRole}.customRoleId`, + { groupCustomRoles: TableName.Role }, + `${TableName.MembershipRole}.customRoleId`, `groupCustomRoles.id` ) - .where(`${TableName.GroupProjectMembership}.projectId`, "=", projectId) + .where(`${TableName.Membership}.scopeProjectId`, "=", projectId) .where((bd) => { if (filterGroupId) { - void bd.where(`${TableName.GroupProjectMembership}.groupId`, "=", filterGroupId); + void bd.where(`${TableName.Membership}.actorGroupId`, "=", filterGroupId); } }) .select( @@ -390,31 +395,19 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { db.ref("name").withSchema(TableName.Groups).as("groupName"), db.ref("slug").withSchema("groupCustomRoles").as("groupProjectMembershipRoleCustomRoleSlug"), db.ref("permissions").withSchema("groupCustomRoles").as("groupProjectMembershipRolePermission"), - db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("groupProjectMembershipRoleId"), - db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("groupProjectMembershipRole"), - db - .ref("customRoleId") - .withSchema(TableName.GroupProjectMembershipRole) - .as("groupProjectMembershipRoleCustomRoleId"), - db - .ref("isTemporary") - .withSchema(TableName.GroupProjectMembershipRole) - .as("groupProjectMembershipRoleIsTemporary"), - db - .ref("temporaryMode") - .withSchema(TableName.GroupProjectMembershipRole) - .as("groupProjectMembershipRoleTemporaryMode"), - db - .ref("temporaryRange") - .withSchema(TableName.GroupProjectMembershipRole) - .as("groupProjectMembershipRoleTemporaryRange"), + db.ref("id").withSchema(TableName.MembershipRole).as("groupProjectMembershipRoleId"), + db.ref("role").withSchema(TableName.MembershipRole).as("groupProjectMembershipRole"), + db.ref("customRoleId").withSchema(TableName.MembershipRole).as("groupProjectMembershipRoleCustomRoleId"), + db.ref("isTemporary").withSchema(TableName.MembershipRole).as("groupProjectMembershipRoleIsTemporary"), + db.ref("temporaryMode").withSchema(TableName.MembershipRole).as("groupProjectMembershipRoleTemporaryMode"), + db.ref("temporaryRange").withSchema(TableName.MembershipRole).as("groupProjectMembershipRoleTemporaryRange"), db .ref("temporaryAccessStartTime") - .withSchema(TableName.GroupProjectMembershipRole) + .withSchema(TableName.MembershipRole) .as("groupProjectMembershipRoleTemporaryAccessStartTime"), db .ref("temporaryAccessEndTime") - .withSchema(TableName.GroupProjectMembershipRole) + .withSchema(TableName.MembershipRole) .as("groupProjectMembershipRoleTemporaryAccessEndTime") ); @@ -476,175 +469,124 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { } }; - const getProjectUserPermissions: TPermissionDALFactory["getProjectUserPermissions"] = async (projectId: string) => { + // TODO(simp): this query is wrong and fix it + const getProjectUserPermissions: TPermissionDALFactory["getProjectUserPermissions"] = async ( + projectId: string, + orgId: string + ) => { try { + const userGroupSubquery = db(TableName.Groups) + .leftJoin(TableName.UserGroupMembership, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`) + .where(`${TableName.Groups}.orgId`, orgId) + .select(db.ref("id").withSchema(TableName.Groups)); + const docs = await db - .replicaNode()(TableName.Users) - .where("isGhost", "=", false) - .leftJoin(TableName.GroupProjectMembership, (queryBuilder) => { - void queryBuilder.on(`${TableName.GroupProjectMembership}.projectId`, db.raw("?", [projectId])); + .replicaNode()(TableName.Membership) + .join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`) + .leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`) + .leftJoin(TableName.AdditionalPrivilege, (qb) => { + qb.on(`${TableName.Membership}.actorUserId`, `${TableName.AdditionalPrivilege}.actorUserId`).andOn( + `${TableName.Membership}.scopeOrgId`, + `${TableName.AdditionalPrivilege}.orgId` + ); }) - .leftJoin( - TableName.GroupProjectMembershipRole, - `${TableName.GroupProjectMembershipRole}.projectMembershipId`, - `${TableName.GroupProjectMembership}.id` - ) - .leftJoin( - { groupCustomRoles: TableName.ProjectRoles }, - `${TableName.GroupProjectMembershipRole}.customRoleId`, - `groupCustomRoles.id` - ) - .join(TableName.ProjectMembership, (queryBuilder) => { - void queryBuilder - .on(`${TableName.ProjectMembership}.projectId`, db.raw("?", [projectId])) - .andOn(`${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`); - }) - .leftJoin( - TableName.ProjectUserMembershipRole, - `${TableName.ProjectUserMembershipRole}.projectMembershipId`, - `${TableName.ProjectMembership}.id` - ) - .leftJoin( - TableName.ProjectRoles, - `${TableName.ProjectUserMembershipRole}.customRoleId`, - `${TableName.ProjectRoles}.id` - ) - .leftJoin(TableName.ProjectUserAdditionalPrivilege, (queryBuilder) => { - void queryBuilder - .on(`${TableName.ProjectUserAdditionalPrivilege}.projectId`, db.raw("?", [projectId])) - .andOn(`${TableName.ProjectUserAdditionalPrivilege}.userId`, `${TableName.Users}.id`); - }) - .join(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId])) - .join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`) .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { void queryBuilder - .on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`) - .andOn(`${TableName.Organization}.id`, `${TableName.IdentityMetadata}.orgId`); + .on(`${TableName.Membership}.actorUserId`, `${TableName.IdentityMetadata}.userId`) + .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); }) + .leftJoin( + TableName.UserGroupMembership, + `${TableName.UserGroupMembership}.groupId`, + `${TableName.Membership}.actorGroupId` + ) + .leftJoin(TableName.Users, (qb) => { + qb.on(`${TableName.Users}.id`, `${TableName.Membership}.actorUserId`).orOn( + `${TableName.Users}.id`, + `${TableName.UserGroupMembership}.groupId` + ); + }) + .where(`${TableName.Membership}.scopeOrgId`, orgId) + .where(`${TableName.Membership}.scope`, AccessScope.Project) + .where(`${TableName.Membership}.scopeProjectId`, projectId) .select( db.ref("id").withSchema(TableName.Users).as("userId"), db.ref("username").withSchema(TableName.Users).as("username"), // groups specific - db.ref("id").withSchema(TableName.GroupProjectMembership).as("groupMembershipId"), - db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipCreatedAt"), - db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipUpdatedAt"), db.ref("slug").withSchema("groupCustomRoles").as("userGroupProjectMembershipRoleCustomRoleSlug"), db.ref("permissions").withSchema("groupCustomRoles").as("userGroupProjectMembershipRolePermission"), - db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRoleId"), - db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRole"), - db - .ref("customRoleId") - .withSchema(TableName.GroupProjectMembershipRole) - .as("userGroupProjectMembershipRoleCustomRoleId"), - db - .ref("isTemporary") - .withSchema(TableName.GroupProjectMembershipRole) - .as("userGroupProjectMembershipRoleIsTemporary"), + db.ref("id").withSchema(TableName.MembershipRole).as("userGroupProjectMembershipRoleId"), + db.ref("role").withSchema(TableName.MembershipRole).as("userGroupProjectMembershipRole"), + db.ref("customRoleId").withSchema(TableName.MembershipRole).as("userGroupProjectMembershipRoleCustomRoleId"), + db.ref("isTemporary").withSchema(TableName.MembershipRole).as("userGroupProjectMembershipRoleIsTemporary"), db .ref("temporaryMode") - .withSchema(TableName.GroupProjectMembershipRole) + .withSchema(TableName.MembershipRole) .as("userGroupProjectMembershipRoleTemporaryMode"), db .ref("temporaryRange") - .withSchema(TableName.GroupProjectMembershipRole) + .withSchema(TableName.MembershipRole) .as("userGroupProjectMembershipRoleTemporaryRange"), db .ref("temporaryAccessStartTime") - .withSchema(TableName.GroupProjectMembershipRole) + .withSchema(TableName.MembershipRole) .as("userGroupProjectMembershipRoleTemporaryAccessStartTime"), db .ref("temporaryAccessEndTime") - .withSchema(TableName.GroupProjectMembershipRole) + .withSchema(TableName.MembershipRole) .as("userGroupProjectMembershipRoleTemporaryAccessEndTime"), // user specific - db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"), - db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"), - db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"), - db.ref("slug").withSchema(TableName.ProjectRoles).as("userProjectMembershipRoleCustomRoleSlug"), - db.ref("permissions").withSchema(TableName.ProjectRoles).as("userProjectCustomRolePermission"), - db.ref("id").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRoleId"), - db.ref("role").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRole"), - db - .ref("temporaryMode") - .withSchema(TableName.ProjectUserMembershipRole) - .as("userProjectMembershipRoleTemporaryMode"), - db - .ref("isTemporary") - .withSchema(TableName.ProjectUserMembershipRole) - .as("userProjectMembershipRoleIsTemporary"), - db - .ref("temporaryRange") - .withSchema(TableName.ProjectUserMembershipRole) - .as("userProjectMembershipRoleTemporaryRange"), + db.ref("id").withSchema(TableName.Membership).as("membershipId"), + db.ref("createdAt").withSchema(TableName.Membership).as("membershipCreatedAt"), + db.ref("updatedAt").withSchema(TableName.Membership).as("membershipUpdatedAt"), + db.ref("slug").withSchema(TableName.Role).as("userProjectMembershipRoleCustomRoleSlug"), + db.ref("permissions").withSchema(TableName.Role).as("userProjectCustomRolePermission"), + db.ref("id").withSchema(TableName.Role).as("userProjectMembershipRoleId"), + db.ref("role").withSchema(TableName.Role).as("userProjectMembershipRole"), + db.ref("temporaryMode").withSchema(TableName.Role).as("userProjectMembershipRoleTemporaryMode"), + db.ref("isTemporary").withSchema(TableName.Role).as("userProjectMembershipRoleIsTemporary"), + db.ref("temporaryRange").withSchema(TableName.Role).as("userProjectMembershipRoleTemporaryRange"), db .ref("temporaryAccessStartTime") - .withSchema(TableName.ProjectUserMembershipRole) + .withSchema(TableName.Role) .as("userProjectMembershipRoleTemporaryAccessStartTime"), db .ref("temporaryAccessEndTime") - .withSchema(TableName.ProjectUserMembershipRole) + .withSchema(TableName.Role) .as("userProjectMembershipRoleTemporaryAccessEndTime"), - db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesId"), - db - .ref("permissions") - .withSchema(TableName.ProjectUserAdditionalPrivilege) - .as("userAdditionalPrivilegesPermissions"), - db - .ref("temporaryMode") - .withSchema(TableName.ProjectUserAdditionalPrivilege) - .as("userAdditionalPrivilegesTemporaryMode"), - db - .ref("isTemporary") - .withSchema(TableName.ProjectUserAdditionalPrivilege) - .as("userAdditionalPrivilegesIsTemporary"), + db.ref("id").withSchema(TableName.AdditionalPrivilege).as("userAdditionalPrivilegesId"), + db.ref("permissions").withSchema(TableName.AdditionalPrivilege).as("userAdditionalPrivilegesPermissions"), + db.ref("temporaryMode").withSchema(TableName.AdditionalPrivilege).as("userAdditionalPrivilegesTemporaryMode"), + db.ref("isTemporary").withSchema(TableName.AdditionalPrivilege).as("userAdditionalPrivilegesIsTemporary"), db .ref("temporaryRange") - .withSchema(TableName.ProjectUserAdditionalPrivilege) + .withSchema(TableName.AdditionalPrivilege) .as("userAdditionalPrivilegesTemporaryRange"), - db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesUserId"), + db.ref("userId").withSchema(TableName.AdditionalPrivilege).as("userAdditionalPrivilegesUserId"), db .ref("temporaryAccessStartTime") - .withSchema(TableName.ProjectUserAdditionalPrivilege) + .withSchema(TableName.AdditionalPrivilege) .as("userAdditionalPrivilegesTemporaryAccessStartTime"), db .ref("temporaryAccessEndTime") - .withSchema(TableName.ProjectUserAdditionalPrivilege) + .withSchema(TableName.AdditionalPrivilege) .as("userAdditionalPrivilegesTemporaryAccessEndTime"), // general db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"), db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"), - db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"), - db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), - db.ref("orgId").withSchema(TableName.Project), - db.ref("type").withSchema(TableName.Project).as("projectType"), - db.ref("id").withSchema(TableName.Project).as("projectId") + db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue") ); const userPermissions = sqlNestRelationships({ data: docs, key: "userId", - parentMapper: ({ - orgId, - username, - orgAuthEnforced, - membershipId, - groupMembershipId, - membershipCreatedAt, - groupMembershipCreatedAt, - groupMembershipUpdatedAt, - membershipUpdatedAt, - projectType, - userId - }) => ({ - orgId, - orgAuthEnforced, + parentMapper: ({ username, membershipId, membershipCreatedAt, membershipUpdatedAt, userId }) => ({ userId, projectId, username, - projectType, - id: membershipId || groupMembershipId, - createdAt: membershipCreatedAt || groupMembershipCreatedAt, - updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt + id: membershipId, + createdAt: membershipCreatedAt, + updatedAt: membershipUpdatedAt }), childrenMapper: [ { @@ -767,68 +709,49 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { }; const getProjectIdentityPermissions: TPermissionDALFactory["getProjectIdentityPermissions"] = async ( - projectId: string + projectId: string, + orgId: string ) => { try { const docs = await db - .replicaNode()(TableName.IdentityProjectMembership) - .join( - TableName.IdentityProjectMembershipRole, - `${TableName.IdentityProjectMembershipRole}.projectMembershipId`, - `${TableName.IdentityProjectMembership}.id` - ) - .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityProjectMembership}.identityId`) - .leftJoin( - TableName.ProjectRoles, - `${TableName.IdentityProjectMembershipRole}.customRoleId`, - `${TableName.ProjectRoles}.id` - ) - .leftJoin( - TableName.IdentityProjectAdditionalPrivilege, - `${TableName.IdentityProjectAdditionalPrivilege}.projectMembershipId`, - `${TableName.IdentityProjectMembership}.id` - ) - .join( - // Join the Project table to later select orgId - TableName.Project, - `${TableName.IdentityProjectMembership}.projectId`, - `${TableName.Project}.id` - ) + .replicaNode()(TableName.Membership) + .join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`) + .leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`) + .leftJoin(TableName.AdditionalPrivilege, (qb) => { + qb.on(`${TableName.Membership}.actorIdentityId`, `${TableName.AdditionalPrivilege}.actorIdentityId`).andOn( + `${TableName.Membership}.scopeOrgId`, + `${TableName.AdditionalPrivilege}.orgId` + ); + }) .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { void queryBuilder - .on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`) - .andOn(`${TableName.Project}.orgId`, `${TableName.IdentityMetadata}.orgId`); + .on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`) + .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); }) - .where(`${TableName.IdentityProjectMembership}.projectId`, projectId) - .select(selectAllTableCols(TableName.IdentityProjectMembershipRole)) + .where(`${TableName.Membership}.scopeOrgId`, orgId) + .where(`${TableName.Membership}.scope`, AccessScope.Project) + .where(`${TableName.Membership}.scopeProjectId`, projectId) + .select(selectAllTableCols(TableName.MembershipRole)) .select( - db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"), + db.ref("id").withSchema(TableName.Membership).as("membershipId"), db.ref("id").withSchema(TableName.Identity).as("identityId"), db.ref("name").withSchema(TableName.Identity).as("identityName"), - db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project - db.ref("type").withSchema(TableName.Project).as("projectType"), - db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"), - db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"), - db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"), - db.ref("permissions").withSchema(TableName.ProjectRoles), - db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"), - db.ref("permissions").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApPermissions"), - db - .ref("temporaryMode") - .withSchema(TableName.IdentityProjectAdditionalPrivilege) - .as("identityApTemporaryMode"), - db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"), - db - .ref("temporaryRange") - .withSchema(TableName.IdentityProjectAdditionalPrivilege) - .as("identityApTemporaryRange"), + db.ref("createdAt").withSchema(TableName.Membership).as("membershipCreatedAt"), + db.ref("updatedAt").withSchema(TableName.Membership).as("membershipUpdatedAt"), + db.ref("slug").withSchema(TableName.Role).as("customRoleSlug"), + db.ref("permissions").withSchema(TableName.Role), + db.ref("id").withSchema(TableName.AdditionalPrivilege).as("identityApId"), + db.ref("permissions").withSchema(TableName.AdditionalPrivilege).as("identityApPermissions"), + db.ref("temporaryMode").withSchema(TableName.AdditionalPrivilege).as("identityApTemporaryMode"), + db.ref("isTemporary").withSchema(TableName.AdditionalPrivilege).as("identityApIsTemporary"), + db.ref("temporaryRange").withSchema(TableName.AdditionalPrivilege).as("identityApTemporaryRange"), db .ref("temporaryAccessStartTime") - .withSchema(TableName.IdentityProjectAdditionalPrivilege) + .withSchema(TableName.AdditionalPrivilege) .as("identityApTemporaryAccessStartTime"), db .ref("temporaryAccessEndTime") - .withSchema(TableName.IdentityProjectAdditionalPrivilege) + .withSchema(TableName.AdditionalPrivilege) .as("identityApTemporaryAccessEndTime"), db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"), db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"), @@ -838,15 +761,7 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { const permissions = sqlNestRelationships({ data: docs, key: "identityId", - parentMapper: ({ - membershipId, - membershipCreatedAt, - membershipUpdatedAt, - orgId, - identityName, - projectType, - identityId - }) => ({ + parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, identityName, identityId }) => ({ id: membershipId, identityId, username: identityName, @@ -854,7 +769,6 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { createdAt: membershipCreatedAt, updatedAt: membershipUpdatedAt, orgId, - projectType, // just a prefilled value orgAuthEnforced: false }), diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 59ca201fc..3ca90a54b 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1262,7 +1262,12 @@ export const registerRoutes = async ( projectTemplateService, smtpService, reminderService, - notificationService + notificationService, + membershipGroupDAL, + membershipIdentityDAL, + membershipRoleDAL, + membershipUserDAL, + roleDAL }); const projectEnvService = projectEnvServiceFactory({ diff --git a/backend/src/server/routes/v1/deprecated-project-membership-router.ts b/backend/src/server/routes/v1/deprecated-project-membership-router.ts index e847be5f0..a04eeedd1 100644 --- a/backend/src/server/routes/v1/deprecated-project-membership-router.ts +++ b/backend/src/server/routes/v1/deprecated-project-membership-router.ts @@ -2,9 +2,11 @@ import { z } from "zod"; import { AccessScope, + OrgMembershipRole, OrgMembershipsSchema, ProjectMembershipsSchema, ProjectUserMembershipRolesSchema, + TemporaryPermissionMode, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas"; @@ -14,7 +16,6 @@ import { ms } from "@app/lib/ms"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types"; export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZodProvider) => { server.route({ @@ -155,7 +156,7 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ membership: { ...membership, userId, - projectId: req.params.workspacId + projectId: req.params.workspaceId } }; } @@ -273,7 +274,10 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ } }); - return { data, success: true }; + return { + data: data.map((el) => ({ ...el, orgId: req.permission.orgId, role: OrgMembershipRole.Member })), + success: true + }; } }); @@ -307,7 +311,7 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ z.object({ role: z.string(), 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"), temporaryAccessStartTime: z.string().datetime() }) @@ -336,7 +340,7 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ scopeData: { scope: AccessScope.Project, orgId: req.permission.orgId, - projectId: req.params.projectId + projectId: req.params.workspaceId }, selector: { userId @@ -375,13 +379,22 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const membership = await server.services.projectMembership.deleteProjectMembership({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId, - membershipId: req.params.membershipId + const { userId } = await server.services.convertor.userMembershipIdToUserId( + req.params.membershipId, + AccessScope.Project, + req.permission.orgId + ); + + const { membership } = await server.services.membershipUser.deleteMembership({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.workspaceId + }, + selector: { + userId + } }); await server.services.auditLog.createAuditLog({ @@ -390,12 +403,19 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ event: { type: EventType.REMOVE_PROJECT_MEMBER, metadata: { - userId: membership.userId, + userId: membership.actorUserId as string, email: "" } } }); - return { membership }; + + return { + membership: { + ...membership, + userId, + projectId: req.params.workspaceId + } + }; } }); }; diff --git a/backend/src/server/routes/v1/org-admin-router.ts b/backend/src/server/routes/v1/org-admin-router.ts index d4b1ee188..8260b3a8f 100644 --- a/backend/src/server/routes/v1/org-admin-router.ts +++ b/backend/src/server/routes/v1/org-admin-router.ts @@ -86,7 +86,7 @@ export const registerOrgAdminRouter = async (server: FastifyZodProvider) => { }); } - return { membership }; + return { membership: { ...membership, userId: req.permission.id, projectId: req.params.projectId } }; } }); }; diff --git a/backend/src/services/additional-privilege/additional-privilege-service.ts b/backend/src/services/additional-privilege/additional-privilege-service.ts index 465f1b47e..ba93608f5 100644 --- a/backend/src/services/additional-privilege/additional-privilege-service.ts +++ b/backend/src/services/additional-privilege/additional-privilege-service.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line simple-import-sort/imports import { RawRule } from "@casl/ability"; import { packRules } from "@casl/ability/extra"; @@ -23,6 +24,7 @@ import { import { newNamespaceAdditionalPrivilegesFactory } from "./namespace/namespace-additional-privilege-factory"; import { newOrgAdditionalPrivilegesFactory } from "./org/org-additional-privilege-factory"; import { newProjectAdditionalPrivilegesFactory } from "./project/project-additional-privilege-factory"; +import { ActorType } from "../auth/auth-type"; type TAdditionalPrivilegeServiceFactoryDep = { additionalPrivilegeDAL: TAdditionalPrivilegeDALFactory; @@ -52,11 +54,14 @@ export const additionalPrivilegeServiceFactory = ({ const createAdditionalPrivilege = async (dto: TCreateAdditionalPrivilegesDTO) => { const { scopeData, data } = dto; const factory = scopeFactory[scopeData.scope]; - const { membershipId } = await factory.onCreateAdditionalPrivilegesGuard(dto); + await factory.onCreateAdditionalPrivilegesGuard(dto); + const scope = factory.getScopeField(dto.scopeData); + const dbActorField = data.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"; const existingSlug = await additionalPrivilegeDAL.findOne({ name: data.name, - membershipId + [dbActorField]: data.actorId, + [scope.key]: scope.value }); if (existingSlug) throw new BadRequestError({ message: `Additional privilege with name ${data.name} exist` }); @@ -66,8 +71,9 @@ export const additionalPrivilegeServiceFactory = ({ if (!data.isTemporary) { const additionalPrivilege = await additionalPrivilegeDAL.create({ - membershipId, name: data.name, + [dbActorField]: data.actorId, + [scope.key]: scope.value, isTemporary: data.isTemporary, permissions: JSON.stringify(packRules(data.permissions as RawRule[])) }); @@ -83,7 +89,8 @@ export const additionalPrivilegeServiceFactory = ({ const relativeTempAllocatedTimeInMs = ms(data.temporaryRange); const additionalPrivilege = await additionalPrivilegeDAL.create({ - membershipId, + [dbActorField]: data.actorId, + [scope.key]: scope.value, name: data.name, isTemporary: data.isTemporary, permissions: data.permissions, @@ -103,11 +110,14 @@ export const additionalPrivilegeServiceFactory = ({ const updateAdditionalPrivilege = async (dto: TUpdateAdditionalPrivilegesDTO) => { const { scopeData, data } = dto; const factory = scopeFactory[scopeData.scope]; - const { membershipId } = await factory.onUpdateAdditionalPrivilegesGuard(dto); + await factory.onUpdateAdditionalPrivilegesGuard(dto); + const scope = factory.getScopeField(dto.scopeData); + const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"; const existingPrivilege = await additionalPrivilegeDAL.findOne({ id: dto.selector.id, - membershipId + [dbActorField]: dto.selector.actorId, + [scope.key]: scope.value }); if (!existingPrivilege) throw new NotFoundError({ message: `Additional privilege with name ${data.name} doesn't exist` }); @@ -135,8 +145,7 @@ export const additionalPrivilegeServiceFactory = ({ } const relativeTempAllocatedTimeInMs = ms(updatedData.temporaryRange); - const additionalPrivilege = await additionalPrivilegeDAL.create({ - membershipId, + const additionalPrivilege = await additionalPrivilegeDAL.updateById(existingPrivilege.id, { name: updatedData.name, isTemporary: updatedData.isTemporary, permissions: updatedData.permissions, @@ -156,11 +165,14 @@ export const additionalPrivilegeServiceFactory = ({ const deleteAdditionalPrivilege = async (dto: TDeleteAdditionalPrivilegesDTO) => { const { scopeData, selector } = dto; const factory = scopeFactory[scopeData.scope]; - const { membershipId } = await factory.onDeleteAdditionalPrivilegesGuard(dto); + await factory.onDeleteAdditionalPrivilegesGuard(dto); + const scope = factory.getScopeField(dto.scopeData); + const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"; const existingPrivilege = await additionalPrivilegeDAL.findOne({ id: selector.id, - membershipId + [dbActorField]: dto.selector.actorId, + [scope.key]: scope.value }); if (!existingPrivilege) throw new NotFoundError({ message: `Additional privilege with id ${selector.id} doesn't exist` }); @@ -174,10 +186,14 @@ export const additionalPrivilegeServiceFactory = ({ const getAdditionalPrivilegeById = async (dto: TGetAdditionalPrivilegesByIdDTO) => { const { scopeData, selector } = dto; const factory = scopeFactory[scopeData.scope]; - const { membershipId } = await factory.onGetAdditionalPrivilegesByIdGuard(dto); + await factory.onGetAdditionalPrivilegesByIdGuard(dto); + const scope = factory.getScopeField(dto.scopeData); + const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"; + const additionalPrivilege = await additionalPrivilegeDAL.findOne({ id: selector.id, - membershipId + [dbActorField]: dto.selector.actorId, + [scope.key]: scope.value }); if (!additionalPrivilege) throw new NotFoundError({ message: `Additional privilege with id ${selector.id} doesn't exist` }); @@ -190,10 +206,14 @@ export const additionalPrivilegeServiceFactory = ({ const getAdditionalPrivilegeByName = async (dto: TGetAdditionalPrivilegesByNameDTO) => { const { scopeData, selector } = dto; const factory = scopeFactory[scopeData.scope]; - const { membershipId } = await factory.onGetAdditionalPrivilegesByIdGuard(dto); + await factory.onGetAdditionalPrivilegesByIdGuard(dto); + const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"; + const scope = factory.getScopeField(dto.scopeData); + const additionalPrivilege = await additionalPrivilegeDAL.findOne({ name: selector.name, - membershipId + [dbActorField]: dto.selector.actorId, + [scope.key]: scope.value }); if (!additionalPrivilege) throw new NotFoundError({ message: `Additional privilege with name ${selector.name} doesn't exist` }); @@ -206,9 +226,13 @@ export const additionalPrivilegeServiceFactory = ({ const listAdditionalPrivileges = async (dto: TListAdditionalPrivilegesDTO) => { const { scopeData } = dto; const factory = scopeFactory[scopeData.scope]; - const { membershipId } = await factory.onListAdditionalPrivilegesGuard(dto); + await factory.onListAdditionalPrivilegesGuard(dto); + const scope = factory.getScopeField(dto.scopeData); + const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"; + const additionalPrivileges = await additionalPrivilegeDAL.find({ - membershipId + [dbActorField]: dto.selector.actorId, + [scope.key]: scope.value }); return { diff --git a/backend/src/services/additional-privilege/additional-privilege-types.ts b/backend/src/services/additional-privilege/additional-privilege-types.ts index f15c4a3c4..eeb5a6a0b 100644 --- a/backend/src/services/additional-privilege/additional-privilege-types.ts +++ b/backend/src/services/additional-privilege/additional-privilege-types.ts @@ -1,14 +1,16 @@ -import { AccessScopeData, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas"; +import { AccessScopeData, TemporaryPermissionMode } from "@app/db/schemas"; import { OrgServiceActor } from "@app/lib/types"; +import { ActorType } from "../auth/auth-type"; + export interface TAdditionalPrivilegesScopeFactory { - onCreateAdditionalPrivilegesGuard: (arg: TCreateAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>; - onUpdateAdditionalPrivilegesGuard: (arg: TUpdateAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>; - onDeleteAdditionalPrivilegesGuard: (arg: TDeleteAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>; - onListAdditionalPrivilegesGuard: (arg: TListAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>; + onCreateAdditionalPrivilegesGuard: (arg: TCreateAdditionalPrivilegesDTO) => Promise; + onUpdateAdditionalPrivilegesGuard: (arg: TUpdateAdditionalPrivilegesDTO) => Promise; + onDeleteAdditionalPrivilegesGuard: (arg: TDeleteAdditionalPrivilegesDTO) => Promise; + onListAdditionalPrivilegesGuard: (arg: TListAdditionalPrivilegesDTO) => Promise; onGetAdditionalPrivilegesByIdGuard: ( arg: TGetAdditionalPrivilegesByIdDTO | TGetAdditionalPrivilegesByNameDTO - ) => Promise<{ membershipId: string }>; + ) => Promise; getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string }; } @@ -17,7 +19,7 @@ export type TCreateAdditionalPrivilegesDTO = { scopeData: AccessScopeData; data: { actorId: string; - actorType: MembershipActors; + actorType: ActorType.USER | ActorType.IDENTITY; name: string; permissions: unknown; isTemporary: boolean; @@ -33,7 +35,7 @@ export type TUpdateAdditionalPrivilegesDTO = { selector: { id: string; actorId: string; - actorType: MembershipActors; + actorType: ActorType.USER | ActorType.IDENTITY; }; data: Partial<{ name: string; @@ -50,7 +52,7 @@ export type TListAdditionalPrivilegesDTO = { scopeData: AccessScopeData; selector: { actorId: string; - actorType: MembershipActors; + actorType: ActorType.USER | ActorType.IDENTITY; }; }; @@ -60,7 +62,7 @@ export type TDeleteAdditionalPrivilegesDTO = { selector: { id: string; actorId: string; - actorType: MembershipActors; + actorType: ActorType.USER | ActorType.IDENTITY; }; }; @@ -70,7 +72,7 @@ export type TGetAdditionalPrivilegesByIdDTO = { selector: { id: string; actorId: string; - actorType: MembershipActors; + actorType: ActorType.USER | ActorType.IDENTITY; }; }; @@ -80,6 +82,6 @@ export type TGetAdditionalPrivilegesByNameDTO = { selector: { name: string; actorId: string; - actorType: MembershipActors; + actorType: ActorType.USER | ActorType.IDENTITY; }; }; diff --git a/backend/src/services/additional-privilege/project/project-additional-privilege-factory.ts b/backend/src/services/additional-privilege/project/project-additional-privilege-factory.ts index 4956f7eac..dd0d2ea55 100644 --- a/backend/src/services/additional-privilege/project/project-additional-privilege-factory.ts +++ b/backend/src/services/additional-privilege/project/project-additional-privilege-factory.ts @@ -1,6 +1,6 @@ import { ForbiddenError } from "@casl/ability"; -import { AccessScope, ActionProjectType, MembershipActors } from "@app/db/schemas"; +import { AccessScope, ActionProjectType } from "@app/db/schemas"; import { constructPermissionErrorMessage, validatePrivilegeChangeOperation @@ -11,7 +11,7 @@ import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors"; +import { BadRequestError, PermissionBoundaryError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; import { ActorType } from "@app/services/auth/auth-type"; import { TMembershipDALFactory } from "@app/services/membership/membership-dal"; @@ -41,16 +41,6 @@ export const newProjectAdditionalPrivilegesFactory = ({ }); }; - const $getActorType = (type: MembershipActors) => { - if (type === MembershipActors.Group) - throw new InternalServerError({ message: "Group additional privilege not implemented for projects" }); - - if (type === MembershipActors.Identity) return ActorType.IDENTITY; - if (type === MembershipActors.User) return ActorType.USER; - - throw new InternalServerError({ message: `Group additional privilege not implemented for actor: ${String(type)}` }); - }; - const getScopeField: TAdditionalPrivilegesScopeFactory["getScopeField"] = (dto) => { if (dto.scope === AccessScope.Project) { return { key: "projectId" as const, value: dto.projectId }; @@ -61,11 +51,11 @@ export const newProjectAdditionalPrivilegesFactory = ({ const onCreateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onCreateAdditionalPrivilegesGuard"] = async (dto) => { const scope = getScopeField(dto.scopeData); - const actorType = $getActorType(dto.data.actorType); const { permission } = await $getPermission(dto.permission, scope.value); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); + const { actorType } = dto.data; const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId); const { permission: targetUserPermission, memberships } = await $getPermission( { ...dto.permission, type: actorType, id: dto.data.actorId }, @@ -100,13 +90,12 @@ export const newProjectAdditionalPrivilegesFactory = ({ (el) => el[actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"] === dto.data.actorId ); if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" }); - return { membershipId: membership.id }; }; const onUpdateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onUpdateAdditionalPrivilegesGuard"] = async (dto) => { const scope = getScopeField(dto.scopeData); - const actorType = $getActorType(dto.selector.actorType); + const { actorType } = dto.selector; const { permission } = await $getPermission(dto.permission, scope.value); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); @@ -145,13 +134,12 @@ export const newProjectAdditionalPrivilegesFactory = ({ (el) => el[actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"] === dto.selector.actorId ); if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" }); - return { membershipId: membership.id }; }; const onDeleteAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onDeleteAdditionalPrivilegesGuard"] = async (dto) => { const scope = getScopeField(dto.scopeData); - const actorType = $getActorType(dto.selector.actorType); + const { actorType } = dto.selector; const { permission } = await $getPermission(dto.permission, scope.value); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); @@ -163,14 +151,13 @@ export const newProjectAdditionalPrivilegesFactory = ({ }); if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" }); - return { membershipId: membership.id }; }; const onListAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onListAdditionalPrivilegesGuard"] = async ( dto ) => { const scope = getScopeField(dto.scopeData); - const actorType = $getActorType(dto.selector.actorType); + const { actorType } = dto.selector; const permissionSet = actorType === ActorType.USER @@ -186,13 +173,12 @@ export const newProjectAdditionalPrivilegesFactory = ({ }); if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" }); - return { membershipId: membership.id }; }; const onGetAdditionalPrivilegesByIdGuard: TAdditionalPrivilegesScopeFactory["onGetAdditionalPrivilegesByIdGuard"] = async (dto) => { const scope = getScopeField(dto.scopeData); - const actorType = $getActorType(dto.selector.actorType); + const { actorType } = dto.selector; const permissionSet = actorType === ActorType.USER @@ -208,7 +194,6 @@ export const newProjectAdditionalPrivilegesFactory = ({ }); if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" }); - return { membershipId: membership.id }; }; return { diff --git a/backend/src/services/convertor/convertor-service.ts b/backend/src/services/convertor/convertor-service.ts index 452f517ad..e049ef504 100644 --- a/backend/src/services/convertor/convertor-service.ts +++ b/backend/src/services/convertor/convertor-service.ts @@ -86,18 +86,20 @@ export const convertorServiceFactory = ({ return { membershipId: membership.id, membership }; }; - const additionalPrivilegeIdToMembershipId = async (privilegeId: string, scope: AccessScope, orgId: string) => { - const membership = await additionalPrivilegeDAL.getMembershipById(privilegeId); - if (!membership || membership.scope !== scope || membership.scopeOrgId !== orgId) { + const additionalPrivilegeIdToDoc = async (privilegeId: string) => { + const doc = await additionalPrivilegeDAL.findOne({ + id: privilegeId + }); + if (!doc) { throw new NotFoundError({ message: `Privilege with id ${privilegeId} not found` }); } - return { membership, membershipId: membership.id }; + return { privilege: doc }; }; - const additionalPrivilegeNameToId = async (privilegeName: string, membershipId: string) => { + const additionalPrivilegeNameToDoc = async (privilegeName: string, projectId: string) => { const privilege = await additionalPrivilegeDAL.findOne({ name: privilegeName, - membershipId + projectId }); if (!privilege) { throw new NotFoundError({ message: `Privilege with slug ${privilegeName} not found` }); @@ -111,8 +113,8 @@ export const convertorServiceFactory = ({ userMembershipIdToUserId, groupMembershipIdToGroupId, identityMembershipIdToIdentityId, - additionalPrivilegeIdToMembershipId, - additionalPrivilegeNameToId, + additionalPrivilegeIdToDoc, + additionalPrivilegeNameToDoc, identityIdToMembershipId }; }; diff --git a/backend/src/services/org-admin/org-admin-service.ts b/backend/src/services/org-admin/org-admin-service.ts index 1a1cefee6..4c080717d 100644 --- a/backend/src/services/org-admin/org-admin-service.ts +++ b/backend/src/services/org-admin/org-admin-service.ts @@ -123,7 +123,8 @@ export const orgAdminServiceFactory = ({ { scopeProjectId: projectId, actorUserId: actorId, - scope: AccessScope.Project + scope: AccessScope.Project, + scopeOrgId: actorOrgId }, tx ); diff --git a/backend/src/services/org/org-dal.ts b/backend/src/services/org/org-dal.ts index 68276509b..d6367dfda 100644 --- a/backend/src/services/org/org-dal.ts +++ b/backend/src/services/org/org-dal.ts @@ -305,7 +305,7 @@ export const orgDALFactory = (db: TDbClient) => { .select( db.ref("id").withSchema(TableName.Membership), db.ref("inviteEmail").withSchema(TableName.Membership), - db.ref("actorOrgId").withSchema(TableName.Membership).as("orgId"), + db.ref("scopeOrgId").withSchema(TableName.Membership).as("orgId"), db.ref("role").withSchema(TableName.MembershipRole), db.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"), db.ref("status").withSchema(TableName.Membership), diff --git a/backend/src/services/role/role-service.ts b/backend/src/services/role/role-service.ts index fc7a66c58..8a1c9c5b1 100644 --- a/backend/src/services/role/role-service.ts +++ b/backend/src/services/role/role-service.ts @@ -2,7 +2,7 @@ import { AccessScope, ActionProjectType, TableName } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; -import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; +import { UnpackedPermissionSchema, unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; import { newNamespaceRoleFactory } from "./namespace/namespace-role-factory"; import { newOrgRoleFactory } from "./org/org-role-factory"; @@ -188,7 +188,7 @@ export const roleServiceFactory = ({ const predefinedRole = await factory.getPredefinedRoles(scopeData); const selectedRole = predefinedRole.find((el) => el.slug === dto.selector.slug); if (!selectedRole) throw new BadRequestError({ message: `Role with slug ${dto.selector.slug} not found` }); - return selectedRole; + return { ...selectedRole, permissions: UnpackedPermissionSchema.array().parse(selectedRole.permissions) }; } const role = await roleDAL.findOne({ diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index bf15500f3..72642f43e 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -946,12 +946,13 @@ export const superAdminServiceFactory = ({ } } + const membershipRole = await membershipRoleDAL.findOne({ membershipId }); const [organizationMembership] = await membershipUserDAL.delete({ scopeOrgId: organizationId, scope: AccessScope.Organization, id: membershipId }); - return organizationMembership; + return { ...organizationMembership, role: membershipRole.role, orgId: organizationId }; }; const joinOrganization = async (orgId: string, actor: OrgServiceActor) => { @@ -988,14 +989,14 @@ export const superAdminServiceFactory = ({ }, tx ); - await membershipRoleDAL.create( + const membershipRole = await membershipRoleDAL.create( { membershipId: membership.id, role: OrgMembershipRole.Admin }, tx ); - return membership; + return { ...membership, role: membershipRole.role, orgId: org.id }; }); return orgMembership; @@ -1052,7 +1053,7 @@ export const superAdminServiceFactory = ({ } }); - return orgMembership; + return { ...orgMembership, orgId: organizationId, role: "" }; }; const getIdentities = async ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => { diff --git a/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx b/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx index dbbc4a806..ca950079a 100644 --- a/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx +++ b/frontend/src/context/OrgPermissionContext/OrgPermissionContext.tsx @@ -10,6 +10,7 @@ import { } from "@app/hooks/api/roles/queries"; import { OrgPermissionSet } from "./types"; +import { useCallback } from "react"; export const useOrgPermission = () => { const organizationId = useRouteContext({ @@ -17,16 +18,24 @@ export const useOrgPermission = () => { select: (el) => el.organizationId }); - const { data } = useSuspenseQuery({ + const { + data: { permission, memberships = [] } + } = useSuspenseQuery({ queryKey: roleQueryKeys.getUserOrgPermissions({ orgId: organizationId }), queryFn: () => fetchUserOrgPermissions({ orgId: organizationId }), select: (res) => { const rule = unpackRules>>(res.permissions); const ability = createMongoAbility(rule, { conditionsMatcher }); - return { permission: ability, membership: res.membership }; + return { permission: ability, memberships: res.memberships }; }, staleTime: Infinity }); - return data; + const hasOrgRole = useCallback( + (role: string) => + memberships?.some((membership) => membership.roles.some((el) => role === el.role)), + [] + ); + + return { permission, memberships, hasOrgRole }; }; diff --git a/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx b/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx index 871d5c338..a5dcdfc16 100644 --- a/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx +++ b/frontend/src/context/ProjectPermissionContext/ProjectPermissionContext.tsx @@ -20,7 +20,7 @@ export const useProjectPermission = () => { } const { - data: { permission, membership, assumedPrivilegeDetails } + data: { permission, memberships, assumedPrivilegeDetails } } = useSuspenseQuery({ queryKey: roleQueryKeys.getUserProjectPermissions({ projectId }), queryFn: () => fetchUserProjectPermissions({ projectId }), @@ -31,18 +31,16 @@ export const useProjectPermission = () => { return { permission: ability, assumedPrivilegeDetails: data.assumedPrivilegeDetails, - membership: { - ...data.membership, - roles: data.membership.roles.map(({ role }) => role) - } + memberships: data.memberships }; } }); const hasProjectRole = useCallback( - (role: string) => membership?.roles?.includes(role) || false, + (role: string) => + memberships?.some((membership) => membership.roles.some((el) => role === el.role)), [] ); - return { permission, membership, hasProjectRole, assumedPrivilegeDetails }; + return { permission, memberships, hasProjectRole, assumedPrivilegeDetails }; }; diff --git a/frontend/src/hooks/api/roles/queries.tsx b/frontend/src/hooks/api/roles/queries.tsx index fd7b28db9..c52b2e3c1 100644 --- a/frontend/src/hooks/api/roles/queries.tsx +++ b/frontend/src/hooks/api/roles/queries.tsx @@ -11,7 +11,7 @@ import { groupBy } from "@app/lib/fn/array"; import { omit } from "@app/lib/fn/object"; import { ActorType } from "../auditLogs/enums"; -import { OrgUser, TProjectMembership } from "../users/types"; +import { TUserMembership } from "../users/types"; import { TGetUserOrgPermissionsDTO, TGetUserProjectPermissionDTO, @@ -109,11 +109,11 @@ export const useGetOrgRole = (orgId: string, roleId: string) => }); export const fetchUserOrgPermissions = async ({ orgId }: TGetUserOrgPermissionsDTO) => { - if (orgId === "") return { permissions: [], membership: null }; + if (orgId === "") return { permissions: [], memberships: [] }; const { data } = await apiRequest.get<{ permissions: PackRule>>[]; - membership: OrgUser; + memberships: Array; }>(`/api/v1/organization/${orgId}/permissions`); return data; @@ -127,7 +127,7 @@ export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) = select: (data) => { const rule = unpackRules>>(data.permissions); const ability = createMongoAbility(rule, { conditionsMatcher }); - return { permission: ability, membership: data.membership }; + return { permission: ability, memberships: data.memberships }; } }); @@ -135,7 +135,7 @@ export const fetchUserProjectPermissions = async ({ projectId }: TGetUserProject const { data } = await apiRequest.get<{ data: { permissions: PackRule>>[]; - membership: Omit & { roles: { role: string }[] }; + memberships: Array; assumedPrivilegeDetails?: { actorId: string; actorType: ActorType; @@ -181,11 +181,7 @@ export const useGetUserProjectPermissions = ({ projectId }: TGetUserProjectPermi }; } }); - const membership = { - ...data.membership, - roles: data.membership.roles.map(({ role }) => role) - }; - return { permission: ability, membership }; + return { permission: ability, memberships: data.memberships }; } }); diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index f442b4c1f..247fd828d 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -72,6 +72,13 @@ export type OrgUser = { lastLoginTime?: string; }; +export type TUserMembership = { + id: string; + scope: string; + scopeOrgId: string; + actorUserId: string; +}; + export type TProjectMembership = { id: string; createdAt: string; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/UpgradePrivilegeSystemModal/UpgradePrivilegeSystemModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/UpgradePrivilegeSystemModal/UpgradePrivilegeSystemModal.tsx index 01bb45598..d44582c71 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/UpgradePrivilegeSystemModal/UpgradePrivilegeSystemModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/UpgradePrivilegeSystemModal/UpgradePrivilegeSystemModal.tsx @@ -7,6 +7,7 @@ import { createNotification } from "@app/components/notifications"; import { Button, Checkbox, Modal, ModalContent, Tooltip } from "@app/components/v2"; import { useOrgPermission } from "@app/context"; import { useUpgradePrivilegeSystem } from "@app/hooks/api"; +import { OrgMembershipRole } from "@app/helpers/roles"; const formSchema = z.object({ isProjectPrivilegesUpdated: z.literal(true), @@ -21,7 +22,7 @@ type Props = { }; export const UpgradePrivilegeSystemModal = ({ isOpen, onOpenChange }: Props) => { - const { membership } = useOrgPermission(); + const { hasOrgRole } = useOrgPermission(); const [step, setStep] = useState<"info" | "upgrade">("info"); const { @@ -65,7 +66,7 @@ export const UpgradePrivilegeSystemModal = ({ isOpen, onOpenChange }: Props) => setStep("info"); }; - const isAdmin = membership?.role === "admin"; + const isAdmin = hasOrgRole(OrgMembershipRole.Admin); return ( @@ -274,7 +275,7 @@ export const UpgradePrivilegeSystemModal = ({ isOpen, onOpenChange }: Props) => diff --git a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/ExternalMigrationsTab.tsx b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/ExternalMigrationsTab.tsx index 8c6d511b1..3b143e742 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/ExternalMigrationsTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/ExternalMigrationsTab.tsx @@ -4,12 +4,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button } from "@app/components/v2"; import { useOrgPermission } from "@app/context"; import { usePopUp } from "@app/hooks"; -import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { SelectImportFromPlatformModal } from "./components/SelectImportFromPlatformModal"; +import { OrgMembershipRole } from "@app/helpers/roles"; export const ExternalMigrationsTab = () => { - const { membership } = useOrgPermission(); + const { hasOrgRole } = useOrgPermission(); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["selectImportPlatform"] as const); @@ -41,7 +41,7 @@ export const ExternalMigrationsTab = () => { onClick={() => { handlePopUpOpen("selectImportPlatform"); }} - isDisabled={membership?.role !== ProjectMembershipRole.Admin} + isDisabled={hasOrgRole(OrgMembershipRole.Admin)} leftIcon={} > Import diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx index 2050d53b7..c6a7bb2a0 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx @@ -6,12 +6,13 @@ import { useOrganization, useOrgPermission } from "@app/context"; import { useDeleteOrgById } from "@app/hooks/api"; import { clearSession } from "@app/hooks/api/users/queries"; import { usePopUp } from "@app/hooks/usePopUp"; +import { OrgMembershipRole } from "@app/helpers/roles"; export const OrgDeleteSection = () => { const navigate = useNavigate(); const { currentOrg } = useOrganization(); - const { membership } = useOrgPermission(); + const { hasOrgRole } = useOrgPermission(); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["deleteOrg"] as const); @@ -52,7 +53,7 @@ export const OrgDeleteSection = () => { variant="outline_bg" type="submit" onClick={() => handlePopUpOpen("deleteOrg")} - isDisabled={Boolean(membership && membership.role !== "admin")} + isDisabled={Boolean(!hasOrgRole(OrgMembershipRole.Admin))} > {`Delete ${currentOrg?.name}`} diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx index d48113d4c..03e22b3e4 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx @@ -3,14 +3,15 @@ import { useOrgPermission } from "@app/context"; import { OrgDeleteSection } from "../OrgDeleteSection"; import { OrgIncidentContactsSection } from "../OrgIncidentContactsSection"; import { OrgNameChangeSection } from "../OrgNameChangeSection"; +import { OrgMembershipRole } from "@app/helpers/roles"; export const OrgGeneralTab = () => { - const { membership } = useOrgPermission(); + const { hasOrgRole } = useOrgPermission(); return (
- {membership && membership.role === "admin" && } + {hasOrgRole(OrgMembershipRole.Admin) && }
); }; diff --git a/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx b/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx index 11f4f0270..74e0eda34 100644 --- a/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx @@ -20,7 +20,7 @@ export const AuditLogsRetentionSection = () => { const { mutateAsync: updateAuditLogsRetention } = useUpdateWorkspaceAuditLogsRetention(); const { currentProject } = useProject(); - const { membership } = useProjectPermission(); + const { hasProjectRole } = useProjectPermission(); const { subscription } = useSubscription(); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const); @@ -82,7 +82,7 @@ export const AuditLogsRetentionSection = () => { return null; } - const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin); + const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); return ( <>
diff --git a/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx b/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx index 4771e0c88..fd0be1d7e 100644 --- a/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx @@ -10,7 +10,8 @@ import { ProjectPermissionSub, useOrganization, useProject, - useProjectPermission + useProjectPermission, + useUser } from "@app/context"; import { useToggle } from "@app/hooks"; import { useDeleteWorkspace, useGetWorkspaceUsers, useLeaveProject } from "@app/hooks/api"; @@ -24,8 +25,9 @@ export const DeleteProjectSection = () => { "leaveWorkspace" ] as const); + const { user } = useUser(); const { currentOrg } = useOrganization(); - const { hasProjectRole, membership } = useProjectPermission(); + const { hasProjectRole } = useProjectPermission(); const { currentProject } = useProject(); const [isDeleting, setIsDeleting] = useToggle(); const [isLeaving, setIsLeaving] = useToggle(); @@ -39,14 +41,14 @@ export const DeleteProjectSection = () => { const isNoAccessMember = hasProjectRole("no-access"); const isOnlyAdminMember = useMemo(() => { - if (!members || !membership || !hasProjectRole("admin")) return false; + if (!members || !hasProjectRole("admin")) return false; const adminMembers = members.filter( - (member) => member.roles.map((r) => r.role).includes("admin") && member.id !== membership.id // exclude the current user + (member) => member.roles.map((r) => r.role).includes("admin") && member.user.id !== user.id // exclude the current user ); return !adminMembers.length; - }, [members, membership]); + }, [members, user]); const handleDeleteWorkspaceSubmit = async () => { setIsDeleting.on(); diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx index 048589827..433147429 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx @@ -38,7 +38,7 @@ export const SecretV2MigrationSection = () => { currentProject?.upgradeStatus === ProjectUpgradeStatus.InProgress ? 2000 : false } ); - const { membership } = useProjectPermission(); + const { hasProjectRole } = useProjectPermission(); const migrateProjectToV3 = useMigrateProjectToV3(); const { handleSubmit, control, reset } = useForm({ resolver: zodResolver(formSchema) }); useEffect(() => { @@ -81,7 +81,7 @@ export const SecretV2MigrationSection = () => { } }; - const isAdmin = membership?.roles.includes(ProjectMembershipRole.Admin); + const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); return (
{isUpgrading && ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx index 3e749aa0e..ccd15112f 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx @@ -6,7 +6,7 @@ import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; export const BackfillSecretReferenceSecretion = () => { const { currentProject } = useProject(); - const { membership } = useProjectPermission(); + const { hasProjectRole } = useProjectPermission(); const backfillSecretReferences = useBackfillSecretReference(); if (!currentProject) return null; @@ -21,7 +21,7 @@ export const BackfillSecretReferenceSecretion = () => { } }; - const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin); + const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); return (
diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx index bd8d15887..7a0d2650a 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx @@ -18,7 +18,7 @@ export const PointInTimeVersionLimitSection = () => { const { mutateAsync: updateProject } = useUpdateProject(); const { currentProject, projectId } = useProject(); - const { membership } = useProjectPermission(); + const { hasProjectRole } = useProjectPermission(); const { control, @@ -52,7 +52,7 @@ export const PointInTimeVersionLimitSection = () => { } }; - const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin); + const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); return (
diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx index a7c6bbc36..32bb20429 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx @@ -24,7 +24,7 @@ type TForm = z.infer; export const SecretDetectionIgnoreValuesSection = () => { const { currentProject } = useProject(); - const { membership } = useProjectPermission(); + const { hasProjectRole } = useProjectPermission(); const { mutateAsync: updateProject } = useUpdateProject(); const { @@ -73,7 +73,7 @@ export const SecretDetectionIgnoreValuesSection = () => { } }; - const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin); + const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); if (!currentProject) return null;