mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: added back identity service
This commit is contained in:
21
backend/src/db/migrations/20251023100246_project-identity.ts
Normal file
21
backend/src/db/migrations/20251023100246_project-identity.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasProjectIdCol = await knex.schema.hasColumn(TableName.Identity, "projectId");
|
||||
if (!hasProjectIdCol) {
|
||||
await knex.schema.alterTable(TableName.Identity, (t) => {
|
||||
t.string("projectId");
|
||||
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasProjectIdCol = await knex.schema.hasColumn(TableName.Identity, "projectId");
|
||||
if (hasProjectIdCol) {
|
||||
await knex.schema.alterTable(TableName.Identity, (t) => {
|
||||
t.dropColumn("projectId");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,8 @@ export const IdentitiesSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
hasDeleteProtection: z.boolean().default(false),
|
||||
orgId: z.string().uuid()
|
||||
orgId: z.string().uuid(),
|
||||
projectId: z.string().nullable().optional()
|
||||
});
|
||||
|
||||
export type TIdentities = z.infer<typeof IdentitiesSchema>;
|
||||
|
||||
185
backend/src/services/scoped-identity/identity-dal.ts
Normal file
185
backend/src/services/scoped-identity/identity-dal.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { AccessScope, AccessScopeData, IdentitiesSchema, TableName } from "@app/db/schemas";
|
||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||
|
||||
import { buildAuthMethods } from "../identity/identity-fns";
|
||||
|
||||
export type TIdentityDALFactory = ReturnType<typeof identityDALFactory>;
|
||||
|
||||
export const identityDALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.Identity);
|
||||
|
||||
const getIdentityById = async (scopeData: AccessScopeData, identityId: string) => {
|
||||
const doc = await db
|
||||
.replicaNode()(TableName.Identity)
|
||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||
void queryBuilder
|
||||
.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`)
|
||||
.andOn(`${TableName.Identity}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||
})
|
||||
.leftJoin(
|
||||
TableName.IdentityUniversalAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityUniversalAuth}.identityId`
|
||||
)
|
||||
.leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`)
|
||||
.leftJoin(
|
||||
TableName.IdentityAliCloudAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||
)
|
||||
.leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`)
|
||||
.leftJoin(
|
||||
TableName.IdentityKubernetesAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityKubernetesAuth}.identityId`
|
||||
)
|
||||
.leftJoin(TableName.IdentityOciAuth, `${TableName.Identity}.id`, `${TableName.IdentityOciAuth}.identityId`)
|
||||
.leftJoin(TableName.IdentityOidcAuth, `${TableName.Identity}.id`, `${TableName.IdentityOidcAuth}.identityId`)
|
||||
.leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`)
|
||||
.leftJoin(TableName.IdentityTokenAuth, `${TableName.Identity}.id`, `${TableName.IdentityTokenAuth}.identityId`)
|
||||
.leftJoin(
|
||||
TableName.IdentityTlsCertAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityTlsCertAuth}.identityId`
|
||||
)
|
||||
.leftJoin(TableName.IdentityLdapAuth, `${TableName.Identity}.id`, `${TableName.IdentityLdapAuth}.identityId`)
|
||||
.leftJoin(TableName.IdentityJwtAuth, `${TableName.Identity}.id`, `${TableName.IdentityJwtAuth}.identityId`)
|
||||
.where(`${TableName.Identity}.id`, identityId)
|
||||
.where(`${TableName.Identity}.orgId`, scopeData.orgId)
|
||||
.where((qb) => {
|
||||
if (scopeData.scope === AccessScope.Project) {
|
||||
void qb.where(`${TableName.Identity}.projectId`, scopeData.projectId);
|
||||
} else {
|
||||
void qb.whereNull(`${TableName.Identity}.projectId`);
|
||||
}
|
||||
})
|
||||
.select(
|
||||
selectAllTableCols(TableName.Identity),
|
||||
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("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
||||
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
||||
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
|
||||
db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth),
|
||||
db.ref("id").as("tlsCertId").withSchema(TableName.IdentityTlsCertAuth)
|
||||
);
|
||||
|
||||
if (!doc) return doc;
|
||||
|
||||
const formattedDoc = sqlNestRelationships({
|
||||
data: doc,
|
||||
key: "id",
|
||||
parentMapper: (el) => {
|
||||
const {
|
||||
uaId,
|
||||
awsId,
|
||||
gcpId,
|
||||
kubernetesId,
|
||||
oidcId,
|
||||
azureId,
|
||||
alicloudId,
|
||||
tokenId,
|
||||
jwtId,
|
||||
ociId,
|
||||
ldapId,
|
||||
tlsCertId
|
||||
} = el;
|
||||
return {
|
||||
...IdentitiesSchema.parse(el),
|
||||
authMethods: buildAuthMethods({
|
||||
uaId,
|
||||
awsId,
|
||||
gcpId,
|
||||
kubernetesId,
|
||||
oidcId,
|
||||
azureId,
|
||||
tokenId,
|
||||
alicloudId,
|
||||
jwtId,
|
||||
ldapId,
|
||||
ociId,
|
||||
tlsCertId
|
||||
})
|
||||
};
|
||||
},
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "metadataId",
|
||||
label: "metadata" as const,
|
||||
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||
id: metadataId,
|
||||
key: metadataKey,
|
||||
value: metadataValue
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return formattedDoc?.[0];
|
||||
};
|
||||
|
||||
const listIdentities = async (
|
||||
scopeData: AccessScopeData,
|
||||
filter: { limit?: number; offset?: number; search?: string } = {}
|
||||
) => {
|
||||
const query = db
|
||||
.replicaNode()(TableName.Identity)
|
||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||
void queryBuilder
|
||||
.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`)
|
||||
.andOn(`${TableName.Identity}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||
})
|
||||
.where(`${TableName.Identity}.orgId`, scopeData.orgId)
|
||||
.where((qb) => {
|
||||
if (scopeData.scope === AccessScope.Project) {
|
||||
void qb.where(`${TableName.Identity}.projectId`, scopeData.projectId);
|
||||
} else {
|
||||
void qb.whereNull(`${TableName.Identity}.projectId`);
|
||||
}
|
||||
})
|
||||
.select(
|
||||
selectAllTableCols(TableName.Identity),
|
||||
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
|
||||
)
|
||||
.select(db.raw(`count(distinct ??) over () as ??`, [`${TableName.Identity}.id`, "count"]));
|
||||
|
||||
if (filter.limit) void query.limit(filter.limit);
|
||||
if (filter.offset) void query.offset(filter.offset || 0);
|
||||
|
||||
if (filter.search) void query.whereILike(`${TableName.Identity}.name`, `%${filter.search}%`);
|
||||
|
||||
const docs = await query;
|
||||
|
||||
const formattedDoc = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "id",
|
||||
parentMapper: (el) => IdentitiesSchema.parse(el),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "metadataId",
|
||||
label: "metadata" as const,
|
||||
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||
id: metadataId,
|
||||
key: metadataKey,
|
||||
value: metadataValue
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return { docs: formattedDoc, count: Number((docs?.[0] as unknown as { count: number })?.count) };
|
||||
};
|
||||
|
||||
return { ...orm, listIdentities, getIdentityById };
|
||||
};
|
||||
245
backend/src/services/scoped-identity/identity-service.ts
Normal file
245
backend/src/services/scoped-identity/identity-service.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { AccessScope, OrgMembershipRole } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
|
||||
import { TIdentityMetadataDALFactory } from "../identity/identity-metadata-dal";
|
||||
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
||||
import { TMembershipIdentityDALFactory } from "../membership-identity/membership-identity-dal";
|
||||
import { TIdentityDALFactory } from "./identity-dal";
|
||||
import {
|
||||
TCreateIdentityDTO,
|
||||
TDeleteIdentityDTO,
|
||||
TGetIdentityByIdDTO,
|
||||
TListIdentityDTO,
|
||||
TUpdateIdentityDTO
|
||||
} from "./identity-types";
|
||||
import { newOrgIdentityFactory } from "./org/org-identity-factory";
|
||||
import { newProjectIdentityFactory } from "./project/project-identity-factory";
|
||||
|
||||
type TScopedIdentityServiceFactoryDep = {
|
||||
identityDAL: TIdentityDALFactory;
|
||||
permissionService: TPermissionServiceFactory;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||
membershipIdentityDAL: TMembershipIdentityDALFactory;
|
||||
membershipRoleDAL: TMembershipRoleDALFactory;
|
||||
identityMetadataDAL: TIdentityMetadataDALFactory;
|
||||
};
|
||||
|
||||
export type TScopedIdentityServiceFactory = ReturnType<typeof identityServiceFactory>;
|
||||
|
||||
export const identityServiceFactory = ({
|
||||
identityDAL,
|
||||
permissionService,
|
||||
licenseService,
|
||||
membershipIdentityDAL,
|
||||
membershipRoleDAL,
|
||||
identityMetadataDAL
|
||||
}: TScopedIdentityServiceFactoryDep) => {
|
||||
const orgFactory = newOrgIdentityFactory({
|
||||
permissionService
|
||||
});
|
||||
const projectFactory = newProjectIdentityFactory({
|
||||
permissionService
|
||||
});
|
||||
|
||||
const scopeFactory = {
|
||||
[AccessScope.Organization]: orgFactory,
|
||||
[AccessScope.Project]: projectFactory,
|
||||
// namespace will get stripped off
|
||||
[AccessScope.Namespace]: orgFactory
|
||||
};
|
||||
|
||||
const createIdentity = async (dto: TCreateIdentityDTO) => {
|
||||
const { scopeData, data } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
await factory.onCreateIdentityGuard(dto);
|
||||
|
||||
const plan = await licenseService.getPlan(dto.permission.orgId);
|
||||
|
||||
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||
throw new BadRequestError({
|
||||
message: "Failed to create identity due to identity limit reached. Upgrade plan to create more identities."
|
||||
});
|
||||
}
|
||||
|
||||
const identity = await identityDAL.transaction(async (tx) => {
|
||||
const newIdentity = await identityDAL.create(
|
||||
{
|
||||
name: data.name,
|
||||
hasDeleteProtection: data.hasDeleteProtection,
|
||||
orgId: dto.permission.orgId,
|
||||
projectId: scopeData.scope === AccessScope.Project ? scopeData.projectId : null
|
||||
},
|
||||
tx
|
||||
);
|
||||
const orgMembership = await membershipIdentityDAL.create(
|
||||
{
|
||||
scope: AccessScope.Organization,
|
||||
actorIdentityId: newIdentity.id,
|
||||
scopeOrgId: dto.permission.orgId
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
const newMembershipIds = [orgMembership.id];
|
||||
if (scopeData.scope === AccessScope.Project) {
|
||||
const projectMembership = await membershipIdentityDAL.create(
|
||||
{
|
||||
scope: AccessScope.Project,
|
||||
actorIdentityId: newIdentity.id,
|
||||
scopeOrgId: dto.permission.orgId,
|
||||
scopeProjectId: scopeData.projectId
|
||||
},
|
||||
tx
|
||||
);
|
||||
newMembershipIds.push(projectMembership.id);
|
||||
}
|
||||
|
||||
await membershipRoleDAL.insertMany(
|
||||
newMembershipIds.map((membershipId) => ({
|
||||
membershipId,
|
||||
role: OrgMembershipRole.NoAccess
|
||||
})),
|
||||
tx
|
||||
);
|
||||
|
||||
let insertedMetadata: Array<{
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}> = [];
|
||||
|
||||
if (data.metadata && data.metadata.length) {
|
||||
const rowsToInsert = data.metadata.map(({ key, value }) => ({
|
||||
identityId: newIdentity.id,
|
||||
orgId: dto.permission.orgId,
|
||||
key,
|
||||
value
|
||||
}));
|
||||
|
||||
insertedMetadata = await identityMetadataDAL.insertMany(rowsToInsert, tx);
|
||||
}
|
||||
|
||||
return {
|
||||
...newIdentity,
|
||||
authMethods: [],
|
||||
metadata: insertedMetadata
|
||||
};
|
||||
});
|
||||
await licenseService.updateSubscriptionOrgMemberCount(dto.permission.orgId);
|
||||
|
||||
return { identity };
|
||||
};
|
||||
|
||||
const updateIdentity = async (dto: TUpdateIdentityDTO) => {
|
||||
const { scopeData, data } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
await factory.onUpdateIdentityGuard(dto);
|
||||
const existingIdentity = await identityDAL.findOne({
|
||||
id: dto.selector.identityId,
|
||||
orgId: dto.permission.orgId,
|
||||
projectId: dto.scopeData.scope === AccessScope.Project ? dto.scopeData.projectId : null
|
||||
});
|
||||
if (!existingIdentity)
|
||||
throw new NotFoundError({ message: `Identity with id ${dto.selector.identityId} not found` });
|
||||
|
||||
const identity = await identityDAL.transaction(async (tx) => {
|
||||
const newIdentity =
|
||||
data?.name || data?.hasDeleteProtection
|
||||
? await identityDAL.updateById(
|
||||
dto.selector.identityId,
|
||||
{ name: data.name, hasDeleteProtection: data.hasDeleteProtection },
|
||||
tx
|
||||
)
|
||||
: existingIdentity;
|
||||
|
||||
let insertedMetadata: Array<{
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}> = [];
|
||||
|
||||
if (data.metadata) {
|
||||
await identityMetadataDAL.delete({ orgId: dto.permission.orgId, identityId: dto.selector.identityId }, tx);
|
||||
|
||||
if (data.metadata.length) {
|
||||
const rowsToInsert = data.metadata.map(({ key, value }) => ({
|
||||
identityId: newIdentity.id,
|
||||
orgId: dto.permission.orgId,
|
||||
key,
|
||||
value
|
||||
}));
|
||||
|
||||
insertedMetadata = await identityMetadataDAL.insertMany(rowsToInsert, tx);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...newIdentity,
|
||||
metadata: insertedMetadata
|
||||
};
|
||||
});
|
||||
|
||||
return { identity };
|
||||
};
|
||||
|
||||
const deleteIdentity = async (dto: TDeleteIdentityDTO) => {
|
||||
const { scopeData } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
await factory.onDeleteIdentityGuard(dto);
|
||||
|
||||
const existingIdentity = await identityDAL.findOne({
|
||||
id: dto.selector.identityId,
|
||||
orgId: dto.permission.orgId,
|
||||
projectId: dto.scopeData.scope === AccessScope.Project ? dto.scopeData.projectId : null
|
||||
});
|
||||
if (!existingIdentity)
|
||||
throw new NotFoundError({ message: `Identity with id ${dto.selector.identityId} not found` });
|
||||
|
||||
const deletedIdentity = await identityDAL.deleteById(dto.selector.identityId);
|
||||
|
||||
await licenseService.updateSubscriptionOrgMemberCount(scopeData.orgId);
|
||||
|
||||
return { identity: deletedIdentity };
|
||||
};
|
||||
|
||||
const getIdentityById = async (dto: TGetIdentityByIdDTO) => {
|
||||
const { scopeData } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
await factory.onGetIdentityByIdGuard(dto);
|
||||
|
||||
const identity = await identityDAL.getIdentityById(dto.scopeData, dto.selector.identityId);
|
||||
if (!identity) throw new NotFoundError({ message: `Identity with id ${dto.selector.identityId} not found` });
|
||||
|
||||
return { identity };
|
||||
};
|
||||
|
||||
const listIdentities = async (dto: TListIdentityDTO) => {
|
||||
const { scopeData } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
|
||||
await factory.onListIdentityGuard(dto);
|
||||
|
||||
const identities = await identityDAL.listIdentities(dto.scopeData, {
|
||||
search: dto.data.search,
|
||||
offset: dto.data.offset,
|
||||
limit: dto.data.limit
|
||||
});
|
||||
|
||||
return { identities };
|
||||
};
|
||||
|
||||
return {
|
||||
createIdentity,
|
||||
updateIdentity,
|
||||
deleteIdentity,
|
||||
getIdentityById,
|
||||
listIdentities
|
||||
};
|
||||
};
|
||||
67
backend/src/services/scoped-identity/identity-types.ts
Normal file
67
backend/src/services/scoped-identity/identity-types.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { AccessScopeData } from "@app/db/schemas";
|
||||
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
export interface TIdentityFactory {
|
||||
onCreateIdentityGuard: (arg: TCreateIdentityDTO) => Promise<void>;
|
||||
onUpdateIdentityGuard: (arg: TUpdateIdentityDTO) => Promise<void>;
|
||||
onDeleteIdentityGuard: (arg: TDeleteIdentityDTO) => Promise<void>;
|
||||
onListIdentityGuard: (arg: TListIdentityDTO) => Promise<void>;
|
||||
onGetIdentityByIdGuard: (arg: TGetIdentityByIdDTO) => Promise<void>;
|
||||
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
|
||||
}
|
||||
|
||||
export enum IdentityOrderBy {
|
||||
Name = "name",
|
||||
Role = "role"
|
||||
}
|
||||
|
||||
export type TCreateIdentityDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
data: {
|
||||
name: string;
|
||||
hasDeleteProtection: boolean;
|
||||
metadata?: { key: string; value: string }[];
|
||||
};
|
||||
};
|
||||
|
||||
export type TUpdateIdentityDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
identityId: string;
|
||||
};
|
||||
data: Partial<{
|
||||
name: string;
|
||||
hasDeleteProtection: boolean;
|
||||
metadata?: { key: string; value: string }[];
|
||||
}>;
|
||||
};
|
||||
|
||||
export type TDeleteIdentityDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
identityId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TGetIdentityByIdDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
identityId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TListIdentityDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
data: Partial<{
|
||||
limit: number;
|
||||
offset: number;
|
||||
orderBy: IdentityOrderBy;
|
||||
orderDirection: OrderByDirection;
|
||||
search: string;
|
||||
}>;
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { AccessScope, OrganizationActionScope } from "@app/db/schemas";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { InternalServerError } from "@app/lib/errors";
|
||||
|
||||
import { TIdentityFactory } from "../identity-types";
|
||||
|
||||
type TOrgIdentityFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
};
|
||||
|
||||
export const newOrgIdentityFactory = ({ permissionService }: TOrgIdentityFactoryDep): TIdentityFactory => {
|
||||
const getScopeField: TIdentityFactory["getScopeField"] = (scopeData) => {
|
||||
if (scopeData.scope === AccessScope.Organization) {
|
||||
return { key: "orgId" as const, value: scopeData.orgId };
|
||||
}
|
||||
throw new InternalServerError({ message: "Invalid scope provided for the org factory" });
|
||||
};
|
||||
|
||||
const onCreateIdentityGuard: TIdentityFactory["onCreateIdentityGuard"] = async (dto) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
orgId: dto.permission.orgId,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
actorOrgId: dto.permission.orgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
};
|
||||
|
||||
const onUpdateIdentityGuard: TIdentityFactory["onUpdateIdentityGuard"] = async (dto) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
orgId: dto.permission.orgId,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
actorOrgId: dto.permission.orgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
};
|
||||
|
||||
const onDeleteIdentityGuard: TIdentityFactory["onDeleteIdentityGuard"] = async (dto) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
orgId: dto.permission.orgId,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
actorOrgId: dto.permission.orgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
|
||||
};
|
||||
|
||||
const onListIdentityGuard: TIdentityFactory["onListIdentityGuard"] = async (dto) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
orgId: dto.permission.orgId,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
actorOrgId: dto.permission.orgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
};
|
||||
|
||||
const onGetIdentityByIdGuard: TIdentityFactory["onGetIdentityByIdGuard"] = async (dto) => {
|
||||
const { permission } = await permissionService.getOrgPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
orgId: dto.permission.orgId,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
actorOrgId: dto.permission.orgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateIdentityGuard,
|
||||
onUpdateIdentityGuard,
|
||||
onDeleteIdentityGuard,
|
||||
onListIdentityGuard,
|
||||
onGetIdentityByIdGuard,
|
||||
getScopeField
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { AccessScope, ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { InternalServerError } from "@app/lib/errors";
|
||||
|
||||
import { TIdentityFactory } from "../identity-types";
|
||||
|
||||
type TProjectIdentityFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
};
|
||||
|
||||
export const newProjectIdentityFactory = ({ permissionService }: TProjectIdentityFactoryDep): TIdentityFactory => {
|
||||
const getScopeField: TIdentityFactory["getScopeField"] = (scopeData) => {
|
||||
if (scopeData.scope === AccessScope.Project) {
|
||||
return { key: "projectId" as const, value: scopeData.projectId };
|
||||
}
|
||||
throw new InternalServerError({ message: "Invalid scope provided for the project factory" });
|
||||
};
|
||||
|
||||
const onCreateIdentityGuard: TIdentityFactory["onCreateIdentityGuard"] = async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
projectId: scope.value,
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Create,
|
||||
ProjectPermissionSub.Identity
|
||||
);
|
||||
};
|
||||
|
||||
const onUpdateIdentityGuard: TIdentityFactory["onUpdateIdentityGuard"] = async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
projectId: scope.value,
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
ProjectPermissionSub.Identity
|
||||
);
|
||||
};
|
||||
|
||||
const onDeleteIdentityGuard: TIdentityFactory["onDeleteIdentityGuard"] = async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
projectId: scope.value,
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Delete,
|
||||
ProjectPermissionSub.Identity
|
||||
);
|
||||
};
|
||||
|
||||
const onListIdentityGuard: TIdentityFactory["onListIdentityGuard"] = async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
projectId: scope.value,
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
ProjectPermissionSub.Identity
|
||||
);
|
||||
};
|
||||
|
||||
const onGetIdentityByIdGuard: TIdentityFactory["onGetIdentityByIdGuard"] = async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: dto.permission.type,
|
||||
actorId: dto.permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: dto.permission.authMethod,
|
||||
projectId: scope.value,
|
||||
actorOrgId: dto.permission.orgId
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
ProjectPermissionSub.Identity
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateIdentityGuard,
|
||||
onUpdateIdentityGuard,
|
||||
onDeleteIdentityGuard,
|
||||
onListIdentityGuard,
|
||||
onGetIdentityByIdGuard,
|
||||
getScopeField
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user