feat: go greppy

This commit is contained in:
=
2025-10-09 00:45:49 +05:30
parent fe1bc168f6
commit 97135d9cfa
46 changed files with 150 additions and 106 deletions

View File

@@ -665,7 +665,7 @@ export async function up(knex: Knex): Promise<void> {
await createAdditionalPrivilegeTable(knex);
}
// no mean this has been created before
// this means these tables have been created before
if (hasToMigrateMembershipTable) {
await migrateMembershipData(knex);
}
@@ -784,7 +784,6 @@ const rollbackMembershipRoleData = async (knex: Knex) => {
knex(TableName.MembershipRole)
.join(TableName.Membership, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
.join(TableName.Groups, `${TableName.Membership}.actorGroupId`, `${TableName.Groups}.id`)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.whereNotNull(`${TableName.Membership}.actorGroupId`)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.select(

View File

@@ -281,7 +281,6 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
},
data: {
...req.body,
isTemporary: true,
...updatedInfo,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error this is valid ts

View File

@@ -6,13 +6,15 @@ import { paginateGraphql } from "@octokit/plugin-paginate-graphql";
import { Octokit as OctokitRest } from "@octokit/rest";
import RE2 from "re2";
import { OrgMembershipRole } from "@app/db/schemas";
import { AccessScope, OrgMembershipRole } from "@app/db/schemas";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { retryWithBackoff } from "@app/lib/retry";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TGroupDALFactory } from "../group/group-dal";
@@ -77,6 +79,8 @@ type TGithubOrgSyncServiceFactoryDep = {
"findGroupMembershipsByUserIdInOrg" | "findGroupMembershipsByGroupIdInOrg" | "insertMany" | "delete"
>;
groupDAL: Pick<TGroupDALFactory, "insertMany" | "transaction" | "find">;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany">;
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "insertMany">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOrgMembershipsWithUsersByOrgId">;
};
@@ -90,7 +94,9 @@ export const githubOrgSyncServiceFactory = ({
userGroupMembershipDAL,
groupDAL,
licenseService,
orgMembershipDAL
orgMembershipDAL,
membershipRoleDAL,
membershipGroupDAL
}: TGithubOrgSyncServiceFactoryDep) => {
const createGithubOrgSync = async ({
githubOrgName,
@@ -365,6 +371,25 @@ export const githubOrgSyncServiceFactory = ({
})),
tx
);
const memberships = await membershipGroupDAL.insertMany(
newGroups.map(
(el) => ({
actorGroupId: el.id,
scope: AccessScope.Organization,
scopeOrgId: orgId
}),
tx
)
);
await membershipRoleDAL.insertMany(
memberships.map((el) => ({
membershipId: el.id,
role: OrgMembershipRole.Member
})),
tx
);
await userGroupMembershipDAL.insertMany(
newGroups.map((el) => ({
groupId: el.id,
@@ -691,6 +716,25 @@ export const githubOrgSyncServiceFactory = ({
tx
);
const memberships = await membershipGroupDAL.insertMany(
newGroups.map(
(el) => ({
actorGroupId: el.id,
scope: AccessScope.Organization,
scopeOrgId: orgPermission.orgId
}),
tx
)
);
await membershipRoleDAL.insertMany(
memberships.map((el) => ({
membershipId: el.id,
role: OrgMembershipRole.Member
})),
tx
);
newGroups.forEach((group) => {
if (!existingTeamsMap[group.name]) {
existingTeamsMap[group.name] = [];

View File

@@ -71,7 +71,7 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
const groups: string[] = await (tx || db.replicaNode())(TableName.Membership)
.where(`${TableName.Membership}.scopeProjectId`, projectId)
.whereNot(`${TableName.Membership}.actorGroupId`, groupId)
.pluck(`${TableName.Membership}.groupId`);
.pluck(`${TableName.Membership}.actorGroupId`);
// main query
const members = await (tx || db.replicaNode())(TableName.UserGroupMembership)

View File

@@ -36,7 +36,7 @@ export const licenseDALFactory = (db: TDbClient) => {
void bd.where(`${TableName.Membership}.scopeOrgId`, orgId);
}
})
.join(TableName.Users, `${TableName.Membership}.userId`, `${TableName.Users}.id`)
.join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false)
.count();

View File

@@ -219,7 +219,9 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
}),
z.object({
subject: z.literal(OrgPermissionSubjects.Groups).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionGroupActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(OrgPermissionSubjects.SecretScanning).describe("The entity this permission pertains to."),
@@ -231,7 +233,9 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
}),
z.object({
subject: z.literal(OrgPermissionSubjects.Identity).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionIdentityActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(OrgPermissionSubjects.Kms).describe("The entity this permission pertains to."),

View File

@@ -18,6 +18,7 @@ import { ActorType } from "@app/services/auth/auth-type";
interface TPermissionDataReturn extends TMemberships {
orgAuthEnforced?: boolean | null;
orgGoogleSsoAuthEnforced?: boolean | null;
shouldUseNewPrivilegeSystem?: boolean | null;
bypassOrgAuthEnabled?: boolean | null;
roles: {
id: string;
@@ -223,8 +224,7 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => {
} else if (scopeData.scope === AccessScope.Namespace) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Namespace)
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId)
.whereNull(`${TableName.Membership}.scopeNamespaceId`);
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId);
} else if (scopeData.scope === AccessScope.Project) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Project)
@@ -282,6 +282,7 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => {
parentMapper: (el) =>
MembershipsSchema.extend({
orgAuthEnforced: z.boolean().optional().nullable(),
shouldUseNewPrivilegeSystem: z.boolean().optional().nullable(),
orgGoogleSsoAuthEnforced: z.boolean(),
bypassOrgAuthEnabled: z.boolean()
}).parse(el),

View File

@@ -66,7 +66,12 @@ export type TPermissionServiceFactory = {
actorOrgId: string | undefined
) => Promise<{
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
memberships: Array<TMemberships & { roles: { role: string; customRoleSlug?: string | null }[] }>;
memberships: Array<
TMemberships & {
roles: { role: string; customRoleSlug?: string | null }[];
shouldUseNewPrivilegeSystem?: boolean | null;
}
>;
hasRole: (role: string) => boolean;
}>;
getProjectPermission: (arg: TGetProjectPermissionArg) => Promise<{

View File

@@ -258,10 +258,6 @@ export const permissionServiceFactory = ({
if (!serviceTokenProject) throw new BadRequestError({ message: "Service token not linked to a project" });
if (serviceTokenProject.orgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Service token not a part of the specified organization" });
}
if (serviceToken.projectId !== projectId) {
throw new ForbiddenRequestError({
name: `Service token not a part of the specified project with ID ${projectId}`

View File

@@ -354,11 +354,14 @@ export const scimServiceFactory = ({
},
tx
);
await membershipRoleDAL.create({
membershipId: orgMembership.id,
role,
customRoleId: roleId
});
await membershipRoleDAL.create(
{
membershipId: orgMembership.id,
role,
customRoleId: roleId
},
tx
);
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
orgMembership = await membershipUserDAL.updateById(
orgMembership.id,

View File

@@ -37,6 +37,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
.where(filter)
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.join(TableName.Project, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.join(
TableName.SecretApprovalPolicy,
`${TableName.SecretApprovalRequest}.policyId`,
@@ -111,24 +112,20 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
)
.leftJoin<TMemberships>(db(TableName.Membership).as("approverOrgMembership"), (qb) => {
qb.on(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, `approverOrgMembership.actorUserId`).andOn(
`approverOrgMembership.scope`,
db.raw("?", [AccessScope.Organization])
);
qb.on(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, `approverOrgMembership.actorUserId`)
.andOn(`approverOrgMembership.scopeOrgId`, `${TableName.Project}.orgId`)
.andOn(`approverOrgMembership.scope`, db.raw("?", [AccessScope.Organization]));
})
.leftJoin<TMemberships>(db(TableName.Membership).as("approverGroupOrgMembership"), (qb) => {
qb.on(`secretApprovalPolicyGroupApproverUser.id`, `approverGroupOrgMembership.actorUserId`).andOn(
`approverGroupOrgMembership.scope`,
db.raw("?", [AccessScope.Organization])
);
qb.on(`secretApprovalPolicyGroupApproverUser.id`, `approverGroupOrgMembership.actorUserId`)
.andOn(`approverGroupOrgMembership.scopeOrgId`, `${TableName.Project}.orgId`)
.andOn(`approverGroupOrgMembership.scope`, db.raw("?", [AccessScope.Organization]));
})
.leftJoin<TMemberships>(db(TableName.Membership).as("reviewerOrgMembership"), (qb) => {
qb.on(`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`, `reviewerOrgMembership.actorUserId`).andOn(
`reviewerOrgMembership.scope`,
db.raw("?", [AccessScope.Organization])
);
qb.on(`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`, `reviewerOrgMembership.actorUserId`)
.andOn(`reviewerOrgMembership.scopeOrgId`, `${TableName.Project}.orgId`)
.andOn(`reviewerOrgMembership.scope`, db.raw("?", [AccessScope.Organization]));
})
.select(selectAllTableCols(TableName.SecretApprovalRequest))
.select(

View File

@@ -778,7 +778,9 @@ export const registerRoutes = async (
permissionService,
groupDAL,
userGroupMembershipDAL,
orgMembershipDAL
orgMembershipDAL,
membershipRoleDAL,
membershipGroupDAL
});
const ldapService = ldapConfigServiceFactory({
@@ -1643,7 +1645,6 @@ export const registerRoutes = async (
const identityTlsCertAuthService = identityTlsCertAuthServiceFactory({
identityAccessTokenDAL,
orgDAL,
identityTlsCertAuthDAL,
licenseService,
permissionService,

View File

@@ -12,10 +12,10 @@ import {
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
import { ApiDocsTags, GROUPS, PROJECTS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { isUuidV4 } from "@app/lib/validator";
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 { isUuidV4 } from "@app/lib/validator";
export const registerGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({

View File

@@ -12,10 +12,10 @@ import {
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
import { ApiDocsTags, GROUPS, PROJECTS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { isUuidV4 } from "@app/lib/validator";
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 { isUuidV4 } from "@app/lib/validator";
export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({

View File

@@ -139,6 +139,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
membership: OrgMembershipsSchema.extend({
customRoleSlug: z.string().nullish(),
metadata: z
.object({
key: z.string().trim().min(1),
@@ -224,7 +225,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
return {
membership: {
...membership,
role: "",
role: req.body.role || "",
orgId: req.params.organizationId,
status: membership.status || OrgMembershipStatus.Accepted
}

View File

@@ -63,7 +63,7 @@ export const additionalPrivilegeServiceFactory = ({
[dbActorField]: data.actorId,
[scope.key]: scope.value
});
if (existingSlug) throw new BadRequestError({ message: `Additional privilege with name ${data.name} exist` });
if (existingSlug) throw new BadRequestError({ message: `Additional privilege with name ${data.name} exists` });
validateHandlebarTemplate("Additional Privilege Create", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
@@ -115,12 +115,12 @@ export const additionalPrivilegeServiceFactory = ({
const dbActorField = dto.selector.actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId";
const existingPrivilege = await additionalPrivilegeDAL.findOne({
id: dto.selector.id,
[dbActorField]: dto.selector.actorId,
id: dto.selector.id,
[scope.key]: scope.value
});
if (!existingPrivilege)
throw new NotFoundError({ message: `Additional privilege with name ${data.name} doesn't exist` });
throw new NotFoundError({ message: `Additional privilege with id ${dto.selector.id} doesn't exist` });
validateHandlebarTemplate("Additional Privilege Create", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")

View File

@@ -1,10 +1,10 @@
import { AccessScope } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { NotFoundError } from "@app/lib/errors";
import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
import { TMembershipDALFactory } from "../membership/membership-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
type TConvertorServiceFactoryDep = {
projectDAL: Pick<TProjectDALFactory, "findOne">;
@@ -105,7 +105,7 @@ export const convertorServiceFactory = ({
projectId
});
if (!privilege) {
throw new NotFoundError({ message: `Privilege with slug ${privilegeName} not found` });
throw new NotFoundError({ message: `Privilege with name ${privilegeName} not found` });
}
return { privilegeId: privilege.id, privilege };

View File

@@ -54,7 +54,7 @@ export const externalMigrationServiceFactory = ({
actorAuthMethod,
actorOrgId
);
if (hasRole(OrgMembershipRole.Admin)) {
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can import data" });
}
@@ -102,7 +102,7 @@ export const externalMigrationServiceFactory = ({
actorOrgId
);
if (hasRole(OrgMembershipRole.Admin)) {
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can import data" });
}
@@ -158,7 +158,7 @@ export const externalMigrationServiceFactory = ({
actorOrgId
);
if (hasRole(OrgMembershipRole.Admin)) {
if (!hasRole(OrgMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Only admins can check custom migration status" });
}

View File

@@ -29,7 +29,6 @@ export const groupProjectDALFactory = (db: TDbClient) => {
db.ref("id").as("groupId").withSchema(TableName.Groups),
db.ref("name").as("groupName").withSchema(TableName.Groups),
db.ref("slug").as("groupSlug").withSchema(TableName.Groups),
db.ref("id").withSchema(TableName.Membership),
db.ref("role").withSchema(TableName.MembershipRole),
db.ref("id").withSchema(TableName.MembershipRole).as("membershipRoleId"),
db.ref("customRoleId").withSchema(TableName.MembershipRole),

View File

@@ -257,7 +257,7 @@ export const identityOidcAuthServiceFactory = ({
identityId
});
if (!identityMembershipOrg) {
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
}
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) {
throw new BadRequestError({

View File

@@ -429,7 +429,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
.join(TableName.Identity, `${TableName.Membership}.actorIdentityId`, `${TableName.Identity}.id`)
.where((qb) => {
if (filter.identityId) {
void qb.where("identityId", filter.identityId);
void qb.where(`${TableName.Membership}.actorIdentityId`, filter.identityId);
}
if (filter.search) {

View File

@@ -15,7 +15,7 @@ import {
type TIdentityProjectServiceFactoryDep = {
identityProjectDAL: TIdentityProjectDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
membershipIdentityDAL: TMembershipIdentityDALFactory;
};

View File

@@ -20,7 +20,6 @@ import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identit
import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types";
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
import { TIdentityTlsCertAuthDALFactory } from "./identity-tls-cert-auth-dal";
import { TIdentityTlsCertAuthServiceFactory } from "./identity-tls-cert-auth-types";
@@ -35,7 +34,6 @@ type TIdentityTlsCertAuthServiceFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
orgDAL: Pick<TOrgDALFactory, "findById">;
};
const parseSubjectDetails = (data: string) => {
@@ -53,8 +51,7 @@ export const identityTlsCertAuthServiceFactory = ({
membershipIdentityDAL,
licenseService,
permissionService,
kmsService,
orgDAL
kmsService
}: TIdentityTlsCertAuthServiceFactoryDep): TIdentityTlsCertAuthServiceFactory => {
const login: TIdentityTlsCertAuthServiceFactory["login"] = async ({ identityId, clientCertificate }) => {
const identityTlsCertAuth = await identityTlsCertAuthDAL.findOne({ identityId });
@@ -411,7 +408,7 @@ export const identityTlsCertAuthServiceFactory = ({
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
const { permission: rolePermission, memberships } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identity.id,
identityMembershipOrg.scopeOrgId,
@@ -419,7 +416,7 @@ export const identityTlsCertAuthServiceFactory = ({
actorOrgId
);
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(identityMembershipOrg.scopeOrgId);
const shouldUseNewPrivilegeSystem = Boolean(memberships?.[0]?.shouldUseNewPrivilegeSystem);
const permissionBoundary = validatePrivilegeChangeOperation(
shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,

View File

@@ -404,7 +404,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
.orderBy(
orderBy === OrgIdentityOrderBy.Role
? `${TableName.Membership}.${orderBy}`
? `${TableName.MembershipRole}.${orderBy}`
: `${TableName.Identity}.${orderBy}`,
orderDirection
)

View File

@@ -45,8 +45,7 @@ export const membershipGroupDALFactory = (db: TDbClient) => {
} else if (scopeData.scope === AccessScope.Namespace) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Namespace)
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId)
.whereNull(`${TableName.Membership}.scopeNamespaceId`);
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId);
} else if (scopeData.scope === AccessScope.Project) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Project)
@@ -148,8 +147,7 @@ export const membershipGroupDALFactory = (db: TDbClient) => {
} else if (scopeData.scope === AccessScope.Namespace) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Namespace)
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId)
.whereNull(`${TableName.Membership}.scopeNamespaceId`);
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId);
} else if (scopeData.scope === AccessScope.Project) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Project)

View File

@@ -57,7 +57,7 @@ export const membershipGroupServiceFactory = ({
const hasNoPermanentRole = data.roles.every((el) => el.isTemporary);
if (hasNoPermanentRole) {
throw new BadRequestError({
message: "Group must have atleast one permanent role"
message: "Group must have at least one permanent role"
});
}
const isInvalidTemporaryRole = data.roles.some((el) => {
@@ -151,7 +151,7 @@ export const membershipGroupServiceFactory = ({
const hasNoPermanentRole = data.roles.every((el) => el.isTemporary);
if (hasNoPermanentRole) {
throw new BadRequestError({
message: "Group must have atleast one permanent role"
message: "Group must have at least one permanent role"
});
}
const isInvalidTemporaryRole = data.roles.some((el) => {

View File

@@ -84,7 +84,7 @@ export const newOrgMembershipGroupFactory = ({
const onDeleteMembershipGroupGuard: TMembershipGroupScopeFactory["onDeleteMembershipGroupGuard"] = async () => {
throw new BadRequestError({
message: "Organization membership cannot be created for organization scoped group"
message: "Organization membership cannot be deleted for organization scoped group"
});
};

View File

@@ -120,7 +120,7 @@ export const newProjectMembershipGroupFactory = ({
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to create group project membership",
"Failed to update group project membership",
shouldUseNewPrivilegeSystem,
ProjectPermissionGroupActions.GrantPrivileges,
ProjectPermissionSub.Groups

View File

@@ -52,8 +52,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
} else if (scopeData.scope === AccessScope.Namespace) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Namespace)
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId)
.whereNull(`${TableName.Membership}.scopeNamespaceId`);
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId);
} else if (scopeData.scope === AccessScope.Project) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Project)
@@ -236,8 +235,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
} else if (scopeData.scope === AccessScope.Namespace) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Namespace)
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId)
.whereNull(`${TableName.Membership}.scopeNamespaceId`);
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId);
} else if (scopeData.scope === AccessScope.Project) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Project)

View File

@@ -63,7 +63,7 @@ export const membershipIdentityServiceFactory = ({
const hasNoPermanentRole = data.roles.every((el) => el.isTemporary);
if (hasNoPermanentRole) {
throw new BadRequestError({
message: "Identity must have atleast one permanent role"
message: "Identity must have at least one permanent role"
});
}
const isInvalidTemporaryRole = data.roles.some((el) => {
@@ -157,7 +157,7 @@ export const membershipIdentityServiceFactory = ({
const hasNoPermanentRole = data.roles.every((el) => el.isTemporary);
if (hasNoPermanentRole) {
throw new BadRequestError({
message: "Identity must have atleast one permanent role"
message: "Identity must have at least one permanent role"
});
}
const isInvalidTemporaryRole = data.roles.some((el) => {

View File

@@ -24,31 +24,31 @@ export const newNamespaceMembershipIdentityFactory = (
};
const isCustomRole: TMembershipIdentityScopeFactory["isCustomRole"] = () => {
throw new InternalServerError({ message: "Namespace membership user isCustomRole not implemented" });
throw new InternalServerError({ message: "Namespace membership identity isCustomRole not implemented" });
};
const onCreateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onCreateMembershipIdentityGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace membership user create not implemented" });
throw new InternalServerError({ message: "Namespace membership identity create not implemented" });
};
const onUpdateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onUpdateMembershipIdentityGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace membership user update not implemented" });
throw new InternalServerError({ message: "Namespace membership identity update not implemented" });
};
const onDeleteMembershipIdentityGuard: TMembershipIdentityScopeFactory["onDeleteMembershipIdentityGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace membership user delete not implemented" });
throw new InternalServerError({ message: "Namespace membership identity delete not implemented" });
};
const onListMembershipIdentityGuard: TMembershipIdentityScopeFactory["onListMembershipIdentityGuard"] = async () => {
throw new InternalServerError({ message: "Namespace membership user list not implemented" });
throw new InternalServerError({ message: "Namespace membership identity list not implemented" });
};
const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] =
async () => {
throw new InternalServerError({ message: "Namespace membership user get by user id not implemented" });
throw new InternalServerError({ message: "Namespace membership identity get by identity id not implemented" });
};
return {

View File

@@ -74,7 +74,7 @@ export const newOrgMembershipIdentityFactory = ({
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to create identity org membership",
"Failed to update identity org membership",
shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GrantPrivileges,
OrgPermissionSubjects.Identity
@@ -88,7 +88,7 @@ export const newOrgMembershipIdentityFactory = ({
const onDeleteMembershipIdentityGuard: TMembershipIdentityScopeFactory["onDeleteMembershipIdentityGuard"] =
async () => {
throw new BadRequestError({
message: "Organization membership cannot be created for organization scoped identity"
message: "Organization membership cannot be deleted for organization scoped identity"
});
};

View File

@@ -129,7 +129,7 @@ export const newProjectMembershipIdentityFactory = ({
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to create identity project membership",
"Failed to update identity project membership",
shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity

View File

@@ -51,8 +51,7 @@ export const membershipUserDALFactory = (db: TDbClient) => {
} else if (scopeData.scope === AccessScope.Namespace) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Namespace)
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId)
.whereNull(`${TableName.Membership}.scopeNamespaceId`);
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId);
} else if (scopeData.scope === AccessScope.Project) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Project)
@@ -168,8 +167,7 @@ export const membershipUserDALFactory = (db: TDbClient) => {
} else if (scopeData.scope === AccessScope.Namespace) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Namespace)
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId)
.whereNull(`${TableName.Membership}.scopeNamespaceId`);
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId);
} else if (scopeData.scope === AccessScope.Project) {
void qb
.where(`${TableName.Membership}.scope`, AccessScope.Project)
@@ -192,7 +190,7 @@ export const membershipUserDALFactory = (db: TDbClient) => {
case "role":
return [`${TableName.Role}.slug`, `${TableName.MembershipRole}.role`];
case "username":
return `${TableName.Users}.name`;
return `${TableName.Users}.username`;
default:
throw new BadRequestError({ message: `Invalid ${String(attr)} provided` });
}

View File

@@ -131,13 +131,13 @@ export const membershipUserServiceFactory = ({
existingUsers.push(inviteeUser);
const inviteeUserId = inviteeUser?.id;
const existingEncrytionKey = await userDAL.findUserEncKeyByUserId(inviteeUserId, tx);
const existingEncryptionKey = await userDAL.findUserEncKeyByUserId(inviteeUserId, tx);
// when user is missing the encrytion keys
// this could happen either if user doesn't exist or user didn't find step 3 of generating the encryption keys of srp
// So what we do is we generate a random secure password and then encrypt it with a random pub-private key
// Then when user sign in (as login is not possible as isAccepted is false) we rencrypt the private key with the user password
if (!inviteeUser || (inviteeUser && !inviteeUser?.isAccepted && !existingEncrytionKey)) {
if (!inviteeUser || (inviteeUser && !inviteeUser?.isAccepted && !existingEncryptionKey)) {
await userDAL.createUserEncryption(
{
userId: inviteeUserId,
@@ -159,7 +159,7 @@ export const membershipUserServiceFactory = ({
const hasNoPermanentRole = data.roles.every((el) => el.isTemporary);
if (hasNoPermanentRole) {
throw new BadRequestError({
message: "User must have atleast one permanent role"
message: "User must have at least one permanent role"
});
}
const isInvalidTemporaryRole = data.roles.some((el) => {
@@ -284,7 +284,7 @@ export const membershipUserServiceFactory = ({
const hasNoPermanentRole = data.roles.every((el) => el.isTemporary);
if (hasNoPermanentRole) {
throw new BadRequestError({
message: "User must have atleast one permanent role"
message: "User must have at least one permanent role"
});
}
const isInvalidTemporaryRole = data.roles.some((el) => {

View File

@@ -15,6 +15,7 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
.whereNotNull(`${TableName.Membership}.actorUserId`)
.join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`)
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
.leftJoin(TableName.Role, `${TableName.Role}.id`, `${TableName.MembershipRole}.customRoleId`)
.leftJoin<TUserEncryptionKeys>(
TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`,
@@ -31,6 +32,7 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
db.ref("scopeOrgId").withSchema(TableName.Membership).as("orgId"),
db.ref("role").withSchema(TableName.MembershipRole),
db.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"),
db.ref("slug").withSchema(TableName.Role).as("customRoleSlug"),
db.ref("status").withSchema(TableName.Membership),
db.ref("isActive").withSchema(TableName.Membership),
db.ref("lastLoginAuthMethod").withSchema(TableName.Membership),
@@ -56,6 +58,7 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
parentMapper: ({
email,
isEmailVerified,
customRoleSlug,
username,
firstName,
lastName,
@@ -75,6 +78,7 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
orgId,
id,
role,
customRoleSlug,
status,
isActive,
inviteEmail,

View File

@@ -646,7 +646,7 @@ export const orgDALFactory = (db: TDbClient) => {
.replicaNode()(TableName.Membership)
.where({ actorIdentityId: identityId })
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.whereNotNull(`${TableName.Membership}.actorUserId`)
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
.join(TableName.Organization, `${TableName.Membership}.scopeOrgId`, `${TableName.Organization}.id`)
.select(db.ref("id").withSchema(TableName.Organization).as("id"))

View File

@@ -35,11 +35,11 @@ export const projectKeyDALFactory = (db: TDbClient) => {
const findAllProjectUserPubKeys = async (projectId: string, tx?: Knex) => {
try {
const pubKeys = await (tx || db.replicaNode())(TableName.Membership)
.where(`${TableName.Membership}.scopeProjectId` as "projectId", projectId)
.where(`${TableName.Membership}.scopeProjectId` as "scopeProjectId", projectId)
.where(`${TableName.Membership}.scope`, AccessScope.Project)
.join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`)
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
.select("userId", "publicKey");
.select(db.ref("userId").withSchema(TableName.Users), "publicKey");
return pubKeys;
} catch (error) {
throw new DatabaseError({ error, name: "Find all workspace pub keys" });

View File

@@ -42,12 +42,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
.select("role")
.from(TableName.MembershipRole)
.leftJoin(TableName.Role, `${TableName.Role}.id`, `${TableName.MembershipRole}.customRoleId`)
.whereRaw("??.?? = ??.??", [
TableName.MembershipRole,
"projectMembershipId",
TableName.Membership,
"id"
])
.whereRaw("??.?? = ??.??", [TableName.MembershipRole, "membershipId", TableName.Membership, "id"])
.where((subQb) => {
void subQb
.whereIn(`${TableName.MembershipRole}.role`, filter.roles as string[])

View File

@@ -31,7 +31,6 @@ export const projectDALFactory = (db: TDbClient) => {
.where(`${TableName.Membership}.actorIdentityId`, identityId)
.join(TableName.Project, `${TableName.Membership}.scopeProjectId`, `${TableName.Project}.id`)
.where(`${TableName.Project}.orgId`, orgId)
.join(TableName.Project, `${TableName.Membership}.scopeProjectId`, `${TableName.Project}.id`)
.andWhere((qb) => {
if (projectType) {
void qb.where(`${TableName.Project}.type`, projectType);

View File

@@ -72,7 +72,7 @@ export const roleServiceFactory = ({
slug: data.slug,
[scope.key]: scope.value
});
if (existingRole) throw new NotFoundError({ message: `Role with ${data.slug} exist` });
if (existingRole) throw new NotFoundError({ message: `Role with ${data.slug} exists` });
validateHandlebarTemplate("Role Creation", JSON.stringify(data.permissions || []), {
allowedExpressions: (val) => val.includes("identity.")
@@ -108,7 +108,7 @@ export const roleServiceFactory = ({
[scope.key]: scope.value
});
if (existingSlug && existingRole.id !== existingSlug.id)
throw new BadRequestError({ message: `Role with ${data.slug} not found` });
throw new BadRequestError({ message: `Role with ${data.slug} already exists` });
}
validateHandlebarTemplate("Role Update", JSON.stringify(data.permissions || []), {

View File

@@ -597,7 +597,7 @@ export const superAdminServiceFactory = ({
{
actorIdentityId: newIdentity.id,
scopeOrgId: organization.id,
scope: AccessScope.Project
scope: AccessScope.Organization
},
tx
);
@@ -947,6 +947,9 @@ export const superAdminServiceFactory = ({
}
const membershipRole = await membershipRoleDAL.findOne({ membershipId });
if (!membershipRole) {
throw new NotFoundError({ name: "Membership Role", message: "Membership role not found" });
}
const [organizationMembership] = await membershipUserDAL.delete({
scopeOrgId: organizationId,
scope: AccessScope.Organization,

View File

@@ -64,6 +64,7 @@ export type OrgUser = {
inviteEmail: string;
organization: string;
role: "owner" | "admin" | "member" | "no-access" | "custom";
customRoleSlug?: string;
status: "invited" | "accepted" | "verified" | "completed";
deniedPermissions: any[];
roleId: string;

View File

@@ -272,7 +272,7 @@ export const IdentityAuthTemplatesTable = ({ handlePopUpOpen }: Props) => {
/>
)}
{!subscription.machineIdentityAuthTemplates && (
<EmptyState title="This feature is not been activated for your license." icon={faBan} />
<EmptyState title="This feature has not been activated for your license." icon={faBan} />
)}
{!isPending && templates.length === 0 && (
<EmptyState

View File

@@ -41,7 +41,7 @@ export const ExternalMigrationsTab = () => {
onClick={() => {
handlePopUpOpen("selectImportPlatform");
}}
isDisabled={hasOrgRole(OrgMembershipRole.Admin)}
isDisabled={!hasOrgRole(OrgMembershipRole.Admin)}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Import

View File

@@ -76,7 +76,9 @@ export const UserDetailsSection = ({ membershipId, handlePopUpOpen }: Props) =>
return m.status === "invited" ? "Invited" : "Active";
};
const roleName = roles?.find((r) => r.slug === membership?.role)?.name;
const roleName = roles?.find(
(r) => r.slug === membership?.role || r.slug === membership?.customRoleSlug
)?.name;
return membership ? (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">