feat: switched to projecet gateway, updated icon, cli

This commit is contained in:
=
2025-02-26 20:45:38 +05:30
parent 929a41065c
commit 917573931f
28 changed files with 345 additions and 115 deletions
+43 -11
View File
@@ -1,9 +1,16 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TGateways } from "@app/db/schemas";
import { GatewaysSchema, TableName, TGateways } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
import {
buildFindFilter,
ormify,
selectAllTableCols,
sqlNestRelationships,
TFindFilter,
TFindOpt
} from "@app/lib/knex";
export type TGatewayDALFactory = ReturnType<typeof gatewayDALFactory>;
@@ -16,8 +23,15 @@ export const gatewayDALFactory = (db: TDbClient) => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
.where(buildFindFilter(filter))
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`)
.leftJoin(TableName.ProjectGateway, `${TableName.ProjectGateway}.gatewayId`, `${TableName.Gateway}.id`)
.leftJoin(TableName.Project, `${TableName.Project}.id`, `${TableName.ProjectGateway}.projectId`)
.select(selectAllTableCols(TableName.Gateway))
.select(db.ref("name").withSchema(TableName.Identity).as("identityName"));
.select(
db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("name").withSchema(TableName.Project).as("projectName"),
db.ref("slug").withSchema(TableName.Project).as("projectSlug"),
db.ref("id").withSchema(TableName.Project).as("projectId")
);
if (limit) void query.limit(limit);
if (offset) void query.offset(offset);
if (sort) {
@@ -25,7 +39,25 @@ export const gatewayDALFactory = (db: TDbClient) => {
}
const docs = await query;
return docs.map((el) => ({ ...el, identity: { id: el.identityId, name: el.identityName } }));
return sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (data) => ({
...GatewaysSchema.parse(data),
identity: { id: data.identityId, name: data.identityName }
}),
childrenMapper: [
{
key: "projectId",
label: "projects" as const,
mapper: ({ projectId, projectName, projectSlug }) => ({
id: projectId,
name: projectName,
slug: projectSlug
})
}
]
});
} catch (error) {
throw new DatabaseError({ error, name: `${TableName.Gateway}: Find` });
}
@@ -35,14 +67,14 @@ export const gatewayDALFactory = (db: TDbClient) => {
try {
const query = (tx || db)(TableName.Gateway)
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`)
.join(
TableName.IdentityProjectMembership,
`${TableName.Identity}.id`,
`${TableName.IdentityProjectMembership}.identityId`
)
.join(TableName.ProjectGateway, `${TableName.ProjectGateway}.gatewayId`, `${TableName.Gateway}.id`)
.select(selectAllTableCols(TableName.Gateway))
.select(db.ref("name").withSchema(TableName.Identity).as("identityName"))
.where({ [`${TableName.IdentityProjectMembership}.projectId` as "projectId"]: projectId });
.select(
db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("id").withSchema(TableName.ProjectGateway).as("projectGatewayId")
)
.where({ [`${TableName.ProjectGateway}.projectId` as "projectId"]: projectId });
const docs = await query;
return docs.map((el) => ({ ...el, identity: { id: el.identityId, name: el.identityName } }));
} catch (error) {
@@ -1,6 +1,6 @@
import crypto from "node:crypto";
import { ForbiddenError, subject } from "@casl/ability";
import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import { z } from "zod";
@@ -23,7 +23,6 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
import { TGatewayDALFactory } from "./gateway-dal";
import {
TExchangeAllocatedRelayAddressDTO,
@@ -34,9 +33,11 @@ import {
TUpdateGatewayByIdDTO
} from "./gateway-types";
import { TOrgGatewayConfigDALFactory } from "./org-gateway-config-dal";
import { TProjectGatewayDALFactory } from "./project-gateway-dal";
type TGatewayServiceFactoryDep = {
gatewayDAL: TGatewayDALFactory;
projectGatewayDAL: TProjectGatewayDALFactory;
orgGatewayConfigDAL: Pick<TOrgGatewayConfigDALFactory, "findOne" | "create" | "transaction" | "findById">;
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "getPlan">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "decryptWithRootKey">;
@@ -56,7 +57,8 @@ export const gatewayServiceFactory = ({
kmsService,
permissionService,
orgGatewayConfigDAL,
keyStore
keyStore,
projectGatewayDAL
}: TGatewayServiceFactoryDep) => {
const $validateOrgAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => {
if (!licenseService.onPremFeatures.gateway) {
@@ -410,7 +412,7 @@ export const gatewayServiceFactory = ({
}).cipherTextBlob,
identityId,
orgGatewayRootCaId: orgGatewayConfig.id,
name: `gateway-${alphaNumericNanoId(6)}`
name: `gateway-${alphaNumericNanoId(6).toLowerCase()}`
});
});
@@ -520,7 +522,7 @@ export const gatewayServiceFactory = ({
return gateway;
};
const updateGatewayById = async ({ orgPermission, id, name }: TUpdateGatewayByIdDTO) => {
const updateGatewayById = async ({ orgPermission, id, name, projectIds }: TUpdateGatewayByIdDTO) => {
const { permission } = await permissionService.getOrgPermission(
orgPermission.type,
orgPermission.id,
@@ -537,6 +539,16 @@ export const gatewayServiceFactory = ({
const [gateway] = await gatewayDAL.update({ id, orgGatewayRootCaId: orgGatewayConfig.id }, { name });
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
if (projectIds) {
await projectGatewayDAL.transaction(async (tx) => {
await projectGatewayDAL.delete({ gatewayId: gateway.id }, tx);
await projectGatewayDAL.insertMany(
projectIds.map((el) => ({ gatewayId: gateway.id, projectId: el })),
tx
);
});
}
return gateway;
};
@@ -561,7 +573,7 @@ export const gatewayServiceFactory = ({
};
const getProjectGateways = async ({ projectId, projectPermission }: TGetProjectGatewayByIdDTO) => {
const { permission } = await permissionService.getProjectPermission({
await permissionService.getProjectPermission({
projectId,
actor: projectPermission.type,
actorId: projectPermission.id,
@@ -571,18 +583,16 @@ export const gatewayServiceFactory = ({
});
const gateways = await gatewayDAL.findByProjectId(projectId);
const allowedGateways = gateways.filter((el) =>
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: el.identityId })
)
);
return allowedGateways;
return gateways;
};
// this has no permission check and used for dynamic secrets directly
// assumes permission check is already done
const fnGetGatewayClientTls = async (gatewayId: string) => {
const fnGetGatewayClientTls = async (projectGatewayId: string) => {
const projectGateway = await projectGatewayDAL.findById(projectGatewayId);
if (!projectGateway) throw new NotFoundError({ message: `Project gateway with ID ${projectGatewayId} not found.` });
const { gatewayId } = projectGateway;
const gateway = await gatewayDAL.findById(gatewayId);
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` });
@@ -20,6 +20,7 @@ export type TGetGatewayByIdDTO = {
export type TUpdateGatewayByIdDTO = {
id: string;
name?: string;
projectIds?: string[];
orgPermission: OrgServiceActor;
};
@@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TProjectGatewayDALFactory = ReturnType<typeof projectGatewayDALFactory>;
export const projectGatewayDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.ProjectGateway);
return orm;
};