mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-17 12:56:29 +00:00
feat: completed ap, org roles and project roles and project membership
This commit is contained in:
@@ -1,10 +1,21 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
export type TAdditionalPrivilegeDALFactory = ReturnType<typeof additionalPrivilegeDALFactory>;
|
||||
|
||||
export const additionalPrivilegeDALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.AdditionalPrivilege);
|
||||
return orm;
|
||||
|
||||
const getMembershipById = async (privilegeId: string) => {
|
||||
const doc = await db
|
||||
.replicaNode()(TableName.AdditionalPrivilege)
|
||||
.join(TableName.Membership, `${TableName.AdditionalPrivilege}.membershipId`, `${TableName.Membership}.id`)
|
||||
.select(selectAllTableCols(TableName.Membership))
|
||||
.where(`${TableName.AdditionalPrivilege}.id`, privilegeId)
|
||||
.first();
|
||||
return doc;
|
||||
};
|
||||
|
||||
return { ...orm, getMembershipById };
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
TCreateAdditionalPrivilegesDTO,
|
||||
TDeleteAdditionalPrivilegesDTO,
|
||||
TGetAdditionalPrivilegesByIdDTO,
|
||||
TGetAdditionalPrivilegesByNameDTO,
|
||||
TListAdditionalPrivilegesDTO,
|
||||
TUpdateAdditionalPrivilegesDTO
|
||||
} from "./additional-privilege-types";
|
||||
@@ -71,7 +72,9 @@ export const additionalPrivilegeServiceFactory = ({
|
||||
permissions: JSON.stringify(packRules(data.permissions as RawRule[]))
|
||||
});
|
||||
|
||||
return { additionalPrivilege: { ...additionalPrivilege, permissions: data.permissions } };
|
||||
return {
|
||||
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
|
||||
};
|
||||
}
|
||||
|
||||
if (!data.temporaryAccessStartTime || !data.temporaryRange) {
|
||||
@@ -92,7 +95,9 @@ export const additionalPrivilegeServiceFactory = ({
|
||||
temporaryRange: data.temporaryRange
|
||||
});
|
||||
|
||||
return { additionalPrivilege: { ...additionalPrivilege, permissions: data.permissions } };
|
||||
return {
|
||||
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
|
||||
};
|
||||
};
|
||||
|
||||
const updateAdditionalPrivilege = async (dto: TUpdateAdditionalPrivilegesDTO) => {
|
||||
@@ -169,7 +174,7 @@ export const additionalPrivilegeServiceFactory = ({
|
||||
const getAdditionalPrivilegeById = async (dto: TGetAdditionalPrivilegesByIdDTO) => {
|
||||
const { scopeData, selector } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
const { membershipId } = await factory.onListAdditionalPrivilegesGuard(dto);
|
||||
const { membershipId } = await factory.onGetAdditionalPrivilegesByIdGuard(dto);
|
||||
const additionalPrivilege = await additionalPrivilegeDAL.findOne({
|
||||
id: selector.id,
|
||||
membershipId
|
||||
@@ -182,6 +187,22 @@ export const additionalPrivilegeServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const getAdditionalPrivilegeByName = async (dto: TGetAdditionalPrivilegesByNameDTO) => {
|
||||
const { scopeData, selector } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
const { membershipId } = await factory.onGetAdditionalPrivilegesByIdGuard(dto);
|
||||
const additionalPrivilege = await additionalPrivilegeDAL.findOne({
|
||||
name: selector.name,
|
||||
membershipId
|
||||
});
|
||||
if (!additionalPrivilege)
|
||||
throw new NotFoundError({ message: `Additional privilege with name ${selector.name} doesn't exist` });
|
||||
|
||||
return {
|
||||
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
|
||||
};
|
||||
};
|
||||
|
||||
const listAdditionalPrivileges = async (dto: TListAdditionalPrivilegesDTO) => {
|
||||
const { scopeData } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
@@ -203,6 +224,7 @@ export const additionalPrivilegeServiceFactory = ({
|
||||
updateAdditionalPrivilege,
|
||||
deleteAdditionalPrivilege,
|
||||
getAdditionalPrivilegeById,
|
||||
getAdditionalPrivilegeByName,
|
||||
listAdditionalPrivileges
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,7 +6,9 @@ export interface TAdditionalPrivilegesScopeFactory {
|
||||
onUpdateAdditionalPrivilegesGuard: (arg: TUpdateAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
|
||||
onDeleteAdditionalPrivilegesGuard: (arg: TDeleteAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
|
||||
onListAdditionalPrivilegesGuard: (arg: TListAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
|
||||
onGetAdditionalPrivilegesByIdGuard: (arg: TGetAdditionalPrivilegesByIdDTO) => Promise<{ membershipId: string }>;
|
||||
onGetAdditionalPrivilegesByIdGuard: (
|
||||
arg: TGetAdditionalPrivilegesByIdDTO | TGetAdditionalPrivilegesByNameDTO
|
||||
) => Promise<{ membershipId: string }>;
|
||||
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
|
||||
}
|
||||
|
||||
@@ -71,3 +73,13 @@ export type TGetAdditionalPrivilegesByIdDTO = {
|
||||
actorType: MembershipActors;
|
||||
};
|
||||
};
|
||||
|
||||
export type TGetAdditionalPrivilegesByNameDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
name: string;
|
||||
actorId: string;
|
||||
actorType: MembershipActors;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,88 +1,118 @@
|
||||
import { AccessScope } from "@app/db/schemas";
|
||||
import { NotFoundError } from "@app/lib/errors";
|
||||
|
||||
import { TMembershipDALFactory } from "../membership/membership-dal";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { AccessScope, AccessScopeData } from "@app/db/schemas";
|
||||
import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal";
|
||||
|
||||
type TConvertorServiceFactoryDep = {
|
||||
projectDAL: Pick<TProjectDALFactory, "findOne">;
|
||||
membershipDAL: Pick<TMembershipDALFactory, "findOne">;
|
||||
additionalPrivilegeDAL: Pick<TAdditionalPrivilegeDALFactory, "getMembershipById" | "findOne">;
|
||||
};
|
||||
|
||||
export type TConvertorServiceFactory = ReturnType<typeof convertorServiceFactory>;
|
||||
|
||||
export const convertorServiceFactory = ({ projectDAL, membershipDAL }: TConvertorServiceFactoryDep) => {
|
||||
const $getScopeDatabaseFields = (scopeData: AccessScopeData) => {
|
||||
if (scopeData.scope === AccessScope.Organization) {
|
||||
return { scopeOrgId: scopeData.orgId };
|
||||
}
|
||||
|
||||
if (scopeData.scope === AccessScope.Namespace) {
|
||||
return { scopeNamespaceId: scopeData.namespaceId, scopeOrgId: scopeData.orgId };
|
||||
}
|
||||
|
||||
return { scopeProjectId: scopeData.projectId, scopeOrgId: scopeData.orgId };
|
||||
};
|
||||
|
||||
export const convertorServiceFactory = ({
|
||||
projectDAL,
|
||||
membershipDAL,
|
||||
additionalPrivilegeDAL
|
||||
}: TConvertorServiceFactoryDep) => {
|
||||
const projectSlugToId = async (dto: { slug: string; orgId: string }) => {
|
||||
const project = await projectDAL.findOne({
|
||||
orgId: dto.orgId,
|
||||
slug: dto.slug
|
||||
});
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug ${slug} not found` });
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug ${dto.slug} not found` });
|
||||
return project;
|
||||
};
|
||||
|
||||
const userMembershipIdToUserId = async (scopeData: AccessScopeData, membershipId: string) => {
|
||||
const dbFields = $getScopeDatabaseFields(scopeData);
|
||||
|
||||
const userMembershipIdToUserId = async (membershipId: string, scope: AccessScope, orgId: string) => {
|
||||
const membership = await membershipDAL.findOne({
|
||||
scope: scopeData.scope,
|
||||
scope,
|
||||
id: membershipId,
|
||||
...dbFields,
|
||||
scopeOrgId: scopeData.orgId
|
||||
scopeOrgId: orgId
|
||||
});
|
||||
if (!membership || !membership.actorUserId) {
|
||||
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
|
||||
}
|
||||
return membership.actorUserId;
|
||||
return { userId: membership.actorUserId, membership };
|
||||
};
|
||||
|
||||
const groupMembershipIdToGroupId = async (scopeData: AccessScopeData, membershipId: string) => {
|
||||
const dbFields = $getScopeDatabaseFields(scopeData);
|
||||
|
||||
const groupMembershipIdToGroupId = async (membershipId: string, scope: AccessScope, orgId: string) => {
|
||||
const membership = await membershipDAL.findOne({
|
||||
scope: scopeData.scope,
|
||||
scope,
|
||||
id: membershipId,
|
||||
...dbFields,
|
||||
scopeOrgId: scopeData.orgId
|
||||
scopeOrgId: orgId
|
||||
});
|
||||
if (!membership || !membership.actorGroupId) {
|
||||
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
|
||||
}
|
||||
|
||||
return membership.actorGroupId;
|
||||
return { groupId: membership.actorGroupId, membership };
|
||||
};
|
||||
|
||||
const identityMembershipIdToGroupId = async (scopeData: AccessScopeData, membershipId: string) => {
|
||||
const dbFields = $getScopeDatabaseFields(scopeData);
|
||||
|
||||
const identityMembershipIdToIdentityId = async (membershipId: string, scope: AccessScope, orgId: string) => {
|
||||
const membership = await membershipDAL.findOne({
|
||||
scope: scopeData.scope,
|
||||
scope,
|
||||
id: membershipId,
|
||||
...dbFields,
|
||||
scopeOrgId: scopeData.orgId
|
||||
scopeOrgId: orgId
|
||||
});
|
||||
if (!membership || !membership.actorIdentityId) {
|
||||
throw new NotFoundError({ message: `Membership with id ${membershipId} not found` });
|
||||
}
|
||||
|
||||
return membership.actorIdentityId;
|
||||
return { identityId: membership.actorIdentityId, membership };
|
||||
};
|
||||
|
||||
const identityIdToMembershipId = async (identityId: string, scope: AccessScope, scopeId: string) => {
|
||||
let fieldName = "scopeOrgId";
|
||||
if (scope === AccessScope.Project) {
|
||||
fieldName = "scopeProjectId";
|
||||
} else if (scope === AccessScope.Namespace) {
|
||||
fieldName = "scopeNamespaceId";
|
||||
}
|
||||
|
||||
const membership = await membershipDAL.findOne({
|
||||
scope,
|
||||
actorIdentityId: identityId,
|
||||
[fieldName]: scopeId
|
||||
});
|
||||
|
||||
if (!membership) {
|
||||
throw new NotFoundError({ message: `Identity with id ${identityId} not found` });
|
||||
}
|
||||
|
||||
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) {
|
||||
throw new NotFoundError({ message: `Privilege with id ${privilegeId} not found` });
|
||||
}
|
||||
|
||||
return { membership, membershipId: membership.id };
|
||||
};
|
||||
const additionalPrivilegeNameToId = async (privilegeName: string, membershipId: string) => {
|
||||
const privilege = await additionalPrivilegeDAL.findOne({
|
||||
name: privilegeName,
|
||||
membershipId
|
||||
});
|
||||
if (!privilege) {
|
||||
throw new NotFoundError({ message: `Privilege with slug ${privilegeName} not found` });
|
||||
}
|
||||
|
||||
return { privilegeId: privilege.id, privilege };
|
||||
};
|
||||
|
||||
return {
|
||||
projectSlugToId,
|
||||
userMembershipIdToUserId,
|
||||
groupMembershipIdToGroupId,
|
||||
identityMembershipIdToGroupId
|
||||
identityMembershipIdToIdentityId,
|
||||
additionalPrivilegeIdToMembershipId,
|
||||
additionalPrivilegeNameToId,
|
||||
identityIdToMembershipId
|
||||
};
|
||||
};
|
||||
|
||||
@@ -274,7 +274,7 @@ export const membershipUserServiceFactory = ({
|
||||
const existingMembership = await membershipUserDAL.findOne({
|
||||
scope: scopeData.scope,
|
||||
...scopeDatabaseFields,
|
||||
actorUserId: dto.selector.actorId
|
||||
actorUserId: dto.selector.userId
|
||||
});
|
||||
if (!existingMembership)
|
||||
throw new BadRequestError({
|
||||
@@ -338,31 +338,31 @@ export const membershipUserServiceFactory = ({
|
||||
},
|
||||
tx
|
||||
);
|
||||
await membershipRoleDAL.insertMany(roleDocs, tx);
|
||||
return doc;
|
||||
const insertedRoles = await membershipRoleDAL.insertMany(roleDocs, tx);
|
||||
return { ...doc, roles: insertedRoles };
|
||||
});
|
||||
|
||||
return { memberships: membershipDoc };
|
||||
return { membership: membershipDoc };
|
||||
};
|
||||
|
||||
const deleteMembership = async (dto: TDeleteMembershipUserDTO) => {
|
||||
const { scopeData } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
const { actorIdOfDeletor } = await factory.onDeleteMembershipUserGuard(dto);
|
||||
await factory.onDeleteMembershipUserGuard(dto);
|
||||
|
||||
const scopeDatabaseFields = factory.getScopeDatabaseFields(dto.scopeData);
|
||||
const existingMembership = await membershipUserDAL.findOne({
|
||||
scope: scopeData.scope,
|
||||
...scopeDatabaseFields,
|
||||
actorUserId: dto.selector.actorId
|
||||
actorUserId: dto.selector.userId
|
||||
});
|
||||
if (!existingMembership)
|
||||
throw new BadRequestError({
|
||||
message: "User doesn't have membership"
|
||||
});
|
||||
|
||||
if (existingMembership.actorUserId === actorIdOfDeletor)
|
||||
if (existingMembership.actorUserId === dto.permission.id)
|
||||
throw new BadRequestError({
|
||||
message: "You can delete you own membership"
|
||||
});
|
||||
@@ -403,7 +403,7 @@ export const membershipUserServiceFactory = ({
|
||||
await factory.onGetMembershipUserByUserIdGuard(dto);
|
||||
const membership = await membershipUserDAL.getUserById({
|
||||
scopeData,
|
||||
userId: selector.actorId
|
||||
userId: selector.userId
|
||||
});
|
||||
if (!membership) throw new NotFoundError({ message: `User membership not found` });
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface TMembershipUserScopeFactory {
|
||||
onCreateMembershipComplete: (arg: { id: string; email: string }[]) => Promise<void>;
|
||||
|
||||
onUpdateMembershipUserGuard: (arg: TUpdateMembershipUserDTO) => Promise<void>;
|
||||
onDeleteMembershipUserGuard: (arg: TDeleteMembershipUserDTO) => Promise<{ actorIdOfDeletor: string }>;
|
||||
onDeleteMembershipUserGuard: (arg: TDeleteMembershipUserDTO) => Promise<void>;
|
||||
onListMembershipUserGuard: (arg: TListMembershipUserDTO) => Promise<void>;
|
||||
onGetMembershipUserByUserIdGuard: (arg: TGetMembershipUserByUserIdDTO) => Promise<void>;
|
||||
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
|
||||
@@ -37,7 +37,7 @@ export type TUpdateMembershipUserDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
actorId: string;
|
||||
userId: string;
|
||||
};
|
||||
data: {
|
||||
isActive?: boolean;
|
||||
@@ -67,7 +67,7 @@ export type TDeleteMembershipUserDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
actorId: string;
|
||||
userId: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -75,6 +75,6 @@ export type TGetMembershipUserByUserIdDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
actorId: string;
|
||||
userId: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -63,4 +63,4 @@ export const newNamespaceMembershipUserFactory = (
|
||||
getScopeDatabaseFields,
|
||||
isCustomRole
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -67,7 +67,6 @@ export const newOrgMembershipUserFactory = ({
|
||||
dto.permission.orgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
|
||||
return { actorIdOfDeletor: dto.permission.id };
|
||||
};
|
||||
|
||||
const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => {
|
||||
|
||||
@@ -134,7 +134,6 @@ export const newProjectMembershipUserFactory = ({
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member);
|
||||
return { actorIdOfDeletor: dto.permission.id };
|
||||
};
|
||||
|
||||
const onListMembershipUserGuard: TMembershipUserScopeFactory["onListMembershipUserGuard"] = async (dto) => {
|
||||
|
||||
Reference in New Issue
Block a user