mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 04:27:38 +00:00
feat: added back identity service
This commit is contained in:
@@ -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(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
hasDeleteProtection: z.boolean().default(false),
|
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>;
|
export type TIdentities = z.infer<typeof IdentitiesSchema>;
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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