mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: completed license server integration
This commit is contained in:
25
backend-pg/package-lock.json
generated
25
backend-pg/package-lock.json
generated
@@ -22,6 +22,7 @@
|
||||
"@fastify/swagger-ui": "^1.10.1",
|
||||
"@node-saml/passport-saml": "^4.0.4",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@octokit/webhooks-types": "^7.3.1",
|
||||
"@ucast/mongo2js": "^1.3.4",
|
||||
"ajv": "^8.12.0",
|
||||
"argon2": "^0.31.2",
|
||||
@@ -44,6 +45,7 @@
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"mysql2": "^3.6.5",
|
||||
"nanoid": "^5.0.4",
|
||||
"node-cache": "^5.1.2",
|
||||
"nodemailer": "^6.9.7",
|
||||
"ora": "^7.0.1",
|
||||
"passport-github": "^1.1.0",
|
||||
@@ -60,7 +62,6 @@
|
||||
"zod-to-json-schema": "^3.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/webhooks-types": "^7.3.1",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/jmespath": "^0.15.2",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
@@ -2313,8 +2314,7 @@
|
||||
"node_modules/@octokit/webhooks-types": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.3.1.tgz",
|
||||
"integrity": "sha512-u6355ZsZnHwmxen30SrqnYb1pXieBFkYgkNzt+Ed4Ao5tupN1OErHfzwiV6hq6duGkDAYASbq7/uVJQ69PjLEg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-u6355ZsZnHwmxen30SrqnYb1pXieBFkYgkNzt+Ed4Ao5tupN1OErHfzwiV6hq6duGkDAYASbq7/uVJQ69PjLEg=="
|
||||
},
|
||||
"node_modules/@octokit/webhooks/node_modules/@octokit/openapi-types": {
|
||||
"version": "12.11.0",
|
||||
@@ -5342,6 +5342,14 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/clone": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||
@@ -8929,6 +8937,17 @@
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
|
||||
},
|
||||
"node_modules/node-cache": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
|
||||
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
|
||||
"dependencies": {
|
||||
"clone": "2.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
|
||||
@@ -78,8 +78,8 @@
|
||||
"@fastify/swagger-ui": "^1.10.1",
|
||||
"@node-saml/passport-saml": "^4.0.4",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@ucast/mongo2js": "^1.3.4",
|
||||
"@octokit/webhooks-types": "^7.3.1",
|
||||
"@ucast/mongo2js": "^1.3.4",
|
||||
"ajv": "^8.12.0",
|
||||
"argon2": "^0.31.2",
|
||||
"aws-sdk": "^2.1532.0",
|
||||
@@ -101,6 +101,7 @@
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"mysql2": "^3.6.5",
|
||||
"nanoid": "^5.0.4",
|
||||
"node-cache": "^5.1.2",
|
||||
"nodemailer": "^6.9.7",
|
||||
"ora": "^7.0.1",
|
||||
"passport-github": "^1.1.0",
|
||||
|
||||
4
backend-pg/src/@types/fastify.d.ts
vendored
4
backend-pg/src/@types/fastify.d.ts
vendored
@@ -3,6 +3,7 @@ import "fastify";
|
||||
import { TUsers } from "@app/db/schemas";
|
||||
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||
@@ -10,6 +11,7 @@ import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-ap
|
||||
import { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
|
||||
import { TSecretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service";
|
||||
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
|
||||
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
|
||||
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
|
||||
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
||||
@@ -101,6 +103,8 @@ declare module "fastify" {
|
||||
saml: TSamlConfigServiceFactory;
|
||||
auditLog: TAuditLogServiceFactory;
|
||||
secretScanning: TSecretScanningServiceFactory;
|
||||
license: TLicenseServiceFactory;
|
||||
trustedIp: TTrustedIpServiceFactory;
|
||||
};
|
||||
|
||||
// this is exclusive use for middlewares in which we need to inject data
|
||||
|
||||
8
backend-pg/src/@types/knex.d.ts
vendored
8
backend-pg/src/@types/knex.d.ts
vendored
@@ -148,6 +148,9 @@ import {
|
||||
TSuperAdmin,
|
||||
TSuperAdminInsert,
|
||||
TSuperAdminUpdate,
|
||||
TTrustedIps,
|
||||
TTrustedIpsInsert,
|
||||
TTrustedIpsUpdate,
|
||||
TUserActions,
|
||||
TUserActionsInsert,
|
||||
TUserActionsUpdate,
|
||||
@@ -393,6 +396,11 @@ declare module "knex/types/tables" {
|
||||
TSecretScanningGitRisksInsert,
|
||||
TSecretScanningGitRisksUpdate
|
||||
>;
|
||||
[TableName.TrustedIps]: Knex.CompositeTableType<
|
||||
TTrustedIps,
|
||||
TTrustedIpsInsert,
|
||||
TTrustedIpsUpdate
|
||||
>;
|
||||
// Junction tables
|
||||
[TableName.JnSecretTag]: Knex.CompositeTableType<
|
||||
TSecretTagJunction,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasTable(TableName.Project))) {
|
||||
await knex.schema.createTable(TableName.Project, (t) => {
|
||||
t.string("id").primary().defaultTo(knex.fn.uuid());
|
||||
t.string("id", 36).primary().defaultTo(knex.fn.uuid());
|
||||
t.string("name").notNullable();
|
||||
t.boolean("autoCapitalization").defaultTo(true);
|
||||
t.uuid("orgId").notNullable();
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasTable(TableName.ServiceToken))) {
|
||||
await knex.schema.createTable(TableName.ServiceToken, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.string("id", 36).primary().defaultTo(knex.fn.uuid());
|
||||
t.string("name").notNullable();
|
||||
t.jsonb("scopes").notNullable();
|
||||
t.specificType("permissions", "text[]").notNullable();
|
||||
|
||||
26
backend-pg/src/db/migrations/20240113103743_trusted-ip.ts
Normal file
26
backend-pg/src/db/migrations/20240113103743_trusted-ip.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasTable(TableName.TrustedIps))) {
|
||||
await knex.schema.createTable(TableName.TrustedIps, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.string("ipAddress").notNullable();
|
||||
t.string("type").notNullable();
|
||||
t.integer("prefix");
|
||||
t.boolean("isActive").defaultTo(true);
|
||||
t.string("comment");
|
||||
t.string("projectId").notNullable();
|
||||
t.foreign("projectId").references("id").inTable(TableName.Project);
|
||||
t.timestamps(true, true, true);
|
||||
});
|
||||
}
|
||||
await createOnUpdateTrigger(knex, TableName.TrustedIps);
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists(TableName.TrustedIps);
|
||||
await dropOnUpdateTrigger(knex, TableName.TrustedIps);
|
||||
}
|
||||
@@ -48,6 +48,7 @@ export * from "./secret-versions";
|
||||
export * from "./secrets";
|
||||
export * from "./service-tokens";
|
||||
export * from "./super-admin";
|
||||
export * from "./trusted-ips";
|
||||
export * from "./user-actions";
|
||||
export * from "./user-encryption-keys";
|
||||
export * from "./users";
|
||||
|
||||
@@ -53,6 +53,7 @@ export enum TableName {
|
||||
GitAppInstallSession = "git_app_install_sessions",
|
||||
GitAppOrg = "git_app_org",
|
||||
SecretScanningGitRisk = "secret_scanning_git_risks",
|
||||
TrustedIps = "trusted_ips",
|
||||
// junction tables
|
||||
JnSecretTag = "secret_tag_junction",
|
||||
JnSecretVersionTag = "secret_version_tag_junction"
|
||||
|
||||
24
backend-pg/src/db/schemas/trusted-ips.ts
Normal file
24
backend-pg/src/db/schemas/trusted-ips.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// Code generated by automation script, DO NOT EDIT.
|
||||
// Automated by pulling database and generating zod schema
|
||||
// To update. Just run npm run generate:schema
|
||||
// Written by akhilmhdh.
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const TrustedIpsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
ipAddress: z.string(),
|
||||
type: z.string(),
|
||||
prefix: z.number().nullable().optional(),
|
||||
isActive: z.boolean().default(true).nullable().optional(),
|
||||
comment: z.string().nullable().optional(),
|
||||
projectId: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
});
|
||||
|
||||
export type TTrustedIps = z.infer<typeof TrustedIpsSchema>;
|
||||
export type TTrustedIpsInsert = Omit<TTrustedIps, TImmutableDBKeys>;
|
||||
export type TTrustedIpsUpdate = Partial<Omit<TTrustedIps, TImmutableDBKeys>>;
|
||||
@@ -1,3 +1,4 @@
|
||||
import { registerLicenseRouter } from "./license-router";
|
||||
import { registerOrgRoleRouter } from "./org-role-router";
|
||||
import { registerProjectRoleRouter } from "./project-role-router";
|
||||
import { registerProjectRouter } from "./project-router";
|
||||
@@ -8,14 +9,17 @@ import { registerSecretRotationProviderRouter } from "./secret-rotation-provider
|
||||
import { registerSecretRotationRouter } from "./secret-rotation-router";
|
||||
import { registerSecretScanningRouter } from "./secret-scanning-router";
|
||||
import { registerSnapshotRouter } from "./snapshot-router";
|
||||
import { registerTrustedIpRouter } from "./trusted-ip-router";
|
||||
|
||||
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||
// org role starts with organization
|
||||
await server.register(registerOrgRoleRouter, { prefix: "/organization" });
|
||||
await server.register(registerLicenseRouter, { prefix: "/organizations" });
|
||||
await server.register(
|
||||
async (projectServer) => {
|
||||
projectServer.register(registerProjectRoleRouter);
|
||||
projectServer.register(registerProjectRouter);
|
||||
async (projectRouter) => {
|
||||
await projectRouter.register(registerProjectRoleRouter);
|
||||
await projectRouter.register(registerProjectRouter);
|
||||
await projectRouter.register(registerTrustedIpRouter);
|
||||
},
|
||||
{ prefix: "/workspace" }
|
||||
);
|
||||
|
||||
365
backend-pg/src/ee/routes/v1/license-router.ts
Normal file
365
backend-pg/src/ee/routes/v1/license-router.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/:organizationId/plans/table",
|
||||
method: "GET",
|
||||
schema: {
|
||||
querystring: z.object({ billingCycle: z.enum(["monthly", "yearly"]) }),
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.getOrgPlansTableByBillCycle({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId,
|
||||
billingCycle: req.query.billingCycle
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/plan",
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.getOrgPlan({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/plans",
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
querystring: z.object({ workspaceId: z.string().trim().optional() }),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.getOrgPlan({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/session/trial",
|
||||
method: "POST",
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({ success_url: z.string().trim() }),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.startOrgTrail({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId,
|
||||
success_url: req.body.success_url
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/plan/billing",
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.getOrgBillingInfo({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/plan/table",
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.getOrgPlanTable({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details",
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.getOrgBillingDetails({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details",
|
||||
method: "PATCH",
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({
|
||||
email: z.string().trim().email().optional(),
|
||||
name: z.string().trim().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.updateOrgBillingDetails({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId,
|
||||
name: req.body.name,
|
||||
email: req.body.email
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/payment-methods",
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.getOrgPmtMethods({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/payment-methods",
|
||||
method: "POST",
|
||||
schema: {
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({
|
||||
success_url: z.string().trim(),
|
||||
cancel_url: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.addOrgPmtMethods({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId,
|
||||
success_url: req.body.success_url,
|
||||
cancel_url: req.body.cancel_url
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/payment-methods/:pmtMethodId",
|
||||
method: "DELETE",
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
pmtMethodId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.delOrgPmtMethods({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId,
|
||||
pmtMethodId: req.params.pmtMethodId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/tax-ids",
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.getOrgTaxIds({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/tax-ids",
|
||||
method: "POST",
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
type: z.string().trim(),
|
||||
value: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.addOrgTaxId({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId,
|
||||
type: req.body.type,
|
||||
value: req.body.value
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/billing-details/tax-ids/:taxId",
|
||||
method: "DELETE",
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
taxId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.delOrgTaxId({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId,
|
||||
taxId: req.params.taxId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/invoices",
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.getOrgTaxInvoices({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:organizationId/licenses",
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.any()
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const data = await server.services.license.getOrgLicenses({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
orgId: req.params.organizationId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
};
|
||||
158
backend-pg/src/ee/routes/v1/trusted-ip-router.ts
Normal file
158
backend-pg/src/ee/routes/v1/trusted-ip-router.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { TrustedIpsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/:workspaceId/trusted-ips",
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
trustedIps: TrustedIpsSchema.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const trustedIps = await server.services.trustedIp.listIpsByProjectId({
|
||||
projectId: req.params.workspaceId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id
|
||||
});
|
||||
return { trustedIps };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:workspaceId/trusted-ips",
|
||||
method: "POST",
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
ipAddress: z.string().trim(),
|
||||
comment: z.string().trim().default(""),
|
||||
isActive: z.boolean()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
trustedIp: TrustedIpsSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { trustedIp, project } = await server.services.trustedIp.addProjectIp({
|
||||
projectId: req.params.workspaceId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
...req.body
|
||||
});
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: project.orgId,
|
||||
projectId: project.id,
|
||||
event: {
|
||||
type: EventType.ADD_TRUSTED_IP,
|
||||
metadata: {
|
||||
trustedIpId: trustedIp.id.toString(),
|
||||
ipAddress: trustedIp.ipAddress,
|
||||
prefix: trustedIp.prefix as number
|
||||
}
|
||||
}
|
||||
});
|
||||
return { trustedIp };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
||||
method: "PATCH",
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
trustedIpId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
ipAddress: z.string().trim(),
|
||||
comment: z.string().trim().default("")
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
trustedIp: TrustedIpsSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { trustedIp, project } = await server.services.trustedIp.updateProjectIp({
|
||||
projectId: req.params.workspaceId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
trustedIpId: req.params.trustedIpId,
|
||||
...req.body
|
||||
});
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: project.orgId,
|
||||
projectId: project.id,
|
||||
event: {
|
||||
type: EventType.UPDATE_TRUSTED_IP,
|
||||
metadata: {
|
||||
trustedIpId: trustedIp.id.toString(),
|
||||
ipAddress: trustedIp.ipAddress,
|
||||
prefix: trustedIp.prefix as number
|
||||
}
|
||||
}
|
||||
});
|
||||
return { trustedIp };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
||||
method: "DELETE",
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
trustedIpId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
trustedIp: TrustedIpsSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { trustedIp, project } = await server.services.trustedIp.deleteProjectIp({
|
||||
projectId: req.params.workspaceId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
trustedIpId: req.params.trustedIpId
|
||||
});
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: project.orgId,
|
||||
projectId: project.id,
|
||||
event: {
|
||||
type: EventType.DELETE_TRUSTED_IP,
|
||||
metadata: {
|
||||
trustedIpId: trustedIp.id.toString(),
|
||||
ipAddress: trustedIp.ipAddress,
|
||||
prefix: trustedIp.prefix as number
|
||||
}
|
||||
}
|
||||
});
|
||||
return { trustedIp };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,18 +1,24 @@
|
||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
import { TProjectDalFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TAuditLogDalFactory } from "./audit-log-dal";
|
||||
import { TCreateAuditLogDTO } from "./audit-log-types";
|
||||
|
||||
type TAuditLogQueueServiceFactoryDep = {
|
||||
auditLogDal: TAuditLogDalFactory;
|
||||
queueService: TQueueServiceFactory;
|
||||
projectDal: Pick<TProjectDalFactory, "findById">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TAuditLogQueueServiceFactory = ReturnType<typeof auditLogQueueServiceFactory>;
|
||||
|
||||
export const auditLogQueueServiceFactory = ({
|
||||
auditLogDal,
|
||||
queueService
|
||||
queueService,
|
||||
projectDal,
|
||||
licenseService
|
||||
}: TAuditLogQueueServiceFactoryDep) => {
|
||||
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
||||
await queueService.queue(QueueName.AuditLog, QueueJobs.AuditLog, data, {
|
||||
@@ -24,8 +30,19 @@ export const auditLogQueueServiceFactory = ({
|
||||
};
|
||||
|
||||
queueService.start(QueueName.AuditLog, async (job) => {
|
||||
const { actor, orgId, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
|
||||
const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
|
||||
let { orgId } = job.data;
|
||||
const MS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
if (!orgId) {
|
||||
// it will never be undefined for both org and project id
|
||||
// TODO(akhilmhdh): use caching here in dal to avoid db calls
|
||||
const project = await projectDal.findById(projectId as string);
|
||||
orgId = project.orgId;
|
||||
}
|
||||
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
const ttl = plan.auditLogsRetentionDays * MS_IN_DAY;
|
||||
await auditLogDal.create({
|
||||
actor: actor.type,
|
||||
actorMetadata: actor.metadata,
|
||||
@@ -34,7 +51,7 @@ export const auditLogQueueServiceFactory = ({
|
||||
ipAddress,
|
||||
orgId,
|
||||
eventType: event.type,
|
||||
expiresAt: new Date(Date.now() + 30 * MS_IN_DAY),
|
||||
expiresAt: new Date(Date.now() + ttl),
|
||||
eventMetadata: event.metadata,
|
||||
userAgentType
|
||||
});
|
||||
|
||||
97
backend-pg/src/ee/services/license/licence-fns.ts
Normal file
97
backend-pg/src/ee/services/license/licence-fns.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import axios, { AxiosError } from "axios";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { request } from "@app/lib/config/request";
|
||||
|
||||
import { TFeatureSet } from "./license-types";
|
||||
|
||||
export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
_id: null,
|
||||
slug: null,
|
||||
tier: -1,
|
||||
workspaceLimit: null,
|
||||
workspacesUsed: 0,
|
||||
memberLimit: null,
|
||||
membersUsed: 0,
|
||||
environmentLimit: null,
|
||||
environmentsUsed: 0,
|
||||
secretVersioning: true,
|
||||
pitRecovery: false,
|
||||
ipAllowlisting: false,
|
||||
rbac: false,
|
||||
customRateLimits: false,
|
||||
customAlerts: false,
|
||||
auditLogs: false,
|
||||
auditLogsRetentionDays: 0,
|
||||
samlSSO: false,
|
||||
status: null,
|
||||
trial_end: null,
|
||||
has_used_trial: true,
|
||||
secretApproval: false,
|
||||
secretRotation: true
|
||||
});
|
||||
|
||||
export const setupLicenceRequestWithStore = (
|
||||
baseURL: string,
|
||||
refreshUrl: string,
|
||||
licenseKey: string
|
||||
) => {
|
||||
let token: string;
|
||||
const licenceReq = axios.create({
|
||||
baseURL,
|
||||
timeout: 15 * 1000,
|
||||
signal: AbortSignal.timeout(15 * 1000)
|
||||
});
|
||||
|
||||
const refreshLicence = async () => {
|
||||
const appCfg = getConfig();
|
||||
const {
|
||||
data: { token: authToken }
|
||||
} = await request.post(
|
||||
refreshUrl,
|
||||
{},
|
||||
{
|
||||
baseURL: appCfg.LICENSE_SERVER_URL,
|
||||
headers: {
|
||||
"X-API-KEY": licenseKey
|
||||
}
|
||||
}
|
||||
);
|
||||
token = authToken;
|
||||
return token;
|
||||
};
|
||||
|
||||
licenceReq.interceptors.request.use(
|
||||
(config) => {
|
||||
if (token && config.headers) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(err) => Promise.reject(err)
|
||||
);
|
||||
|
||||
licenceReq.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (err) => {
|
||||
const originalRequest = err.config;
|
||||
|
||||
// eslint-disable-next-line
|
||||
if ((err as AxiosError)?.response?.status === 401 && !originalRequest._retry) {
|
||||
// eslint-disable-next-line
|
||||
originalRequest._retry = true;
|
||||
|
||||
// refresh
|
||||
await refreshLicence();
|
||||
|
||||
licenceReq.defaults.headers.common.Authorization = `Bearer ${token}`;
|
||||
return licenceReq(originalRequest);
|
||||
}
|
||||
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
|
||||
return { request: licenceReq, refreshLicence };
|
||||
};
|
||||
@@ -1,6 +1,27 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { OrgMembershipStatus, TableName } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
|
||||
export type TLicenseDalFactory = ReturnType<typeof licenseDalFactory>;
|
||||
|
||||
export const licenseDalFactory = (db: TDbClient) => ({ });
|
||||
export const licenseDalFactory = (db: TDbClient) => {
|
||||
const countOfOrgMembers = async (orgId: string | null, tx?: Knex) => {
|
||||
try {
|
||||
const doc = await (tx || db)(TableName.OrgMembership)
|
||||
.where({ status: OrgMembershipStatus.Accepted })
|
||||
.andWhere((bd) => {
|
||||
if (orgId) {
|
||||
bd.where({ orgId });
|
||||
}
|
||||
})
|
||||
.count();
|
||||
return doc?.[0].count;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Count of Org Members" });
|
||||
}
|
||||
};
|
||||
|
||||
return { countOfOrgMembers };
|
||||
};
|
||||
|
||||
@@ -1,34 +1,523 @@
|
||||
import axios from "axios";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import NodeCache from "node-cache";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { TOrgDalFactory } from "@app/services/org/org-dal";
|
||||
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { getDefaultOnPremFeatures, setupLicenceRequestWithStore } from "./licence-fns";
|
||||
import { TLicenseDalFactory } from "./license-dal";
|
||||
import {
|
||||
InstanceType,
|
||||
TAddOrgPmtMethodDTO,
|
||||
TAddOrgTaxIdDTO,
|
||||
TDelOrgPmtMethodDTO,
|
||||
TDelOrgTaxIdDTO,
|
||||
TFeatureSet,
|
||||
TGetOrgBillInfoDTO,
|
||||
TGetOrgTaxIdDTO,
|
||||
TOrgInvoiceDTO,
|
||||
TOrgLicensesDTO,
|
||||
TOrgPlanDTO,
|
||||
TOrgPlansTableDTO,
|
||||
TOrgPmtMethodsDTO,
|
||||
TStartOrgTrailDTO,
|
||||
TUpdateOrgBillingDetailsDTO
|
||||
} from "./license-types";
|
||||
|
||||
type TLicenseServiceFactoryDep = {
|
||||
orgDal: Pick<TOrgDalFactory, "findOrgById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
licenseDal: TLicenseDalFactory;
|
||||
};
|
||||
|
||||
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
|
||||
|
||||
export const licenseServiceFactory = ({ licenseDal }: TLicenseServiceFactoryDep) => {
|
||||
const LICENSE_SERVER_CLOUD_LOGIN = "/api/auth/v1/license-server-login";
|
||||
const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/licence-login";
|
||||
|
||||
const FEATURE_CACHE_KEY = (orgId: string, projectId?: string) => `${orgId}-${projectId || ""}`;
|
||||
export const licenseServiceFactory = ({
|
||||
orgDal,
|
||||
permissionService,
|
||||
licenseDal
|
||||
}: TLicenseServiceFactoryDep) => {
|
||||
let isValidLicense = false;
|
||||
let instanceType = InstanceType.OnPrem;
|
||||
let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures();
|
||||
const featureStore = new NodeCache({ stdTTL: 60 });
|
||||
|
||||
const appCfg = getConfig();
|
||||
const licenceApi = axios.create({
|
||||
baseURL: appCfg.LICENCE_SERVER_URL
|
||||
});
|
||||
const licenseServerCloudApi = setupLicenceRequestWithStore(
|
||||
appCfg.LICENSE_SERVER_URL || "",
|
||||
LICENSE_SERVER_CLOUD_LOGIN,
|
||||
appCfg.LICENSE_SERVER_KEY || ""
|
||||
);
|
||||
|
||||
const licenseServerOnPremApi = setupLicenceRequestWithStore(
|
||||
appCfg.LICENSE_SERVER_URL || "",
|
||||
LICENSE_SERVER_ON_PREM_LOGIN,
|
||||
appCfg.LICENSE_KEY || ""
|
||||
);
|
||||
|
||||
const init = async () => {
|
||||
try {
|
||||
if (appCfg.LICENSE_SERVER_KEY) {
|
||||
const token = await licenseServerCloudApi.refreshLicence();
|
||||
if (token) instanceType = InstanceType.Cloud;
|
||||
logger.info(`Instance type: ${InstanceType.Cloud}`);
|
||||
isValidLicense = true;
|
||||
return;
|
||||
}
|
||||
if (appCfg.LICENSE_KEY) {
|
||||
const token = await licenseServerOnPremApi.refreshLicence();
|
||||
if (token) {
|
||||
const {
|
||||
data: { currentPlan }
|
||||
} = await licenseServerOnPremApi.request.get<{ currentPlan: TFeatureSet }>(
|
||||
"/api/license/v1/plan"
|
||||
);
|
||||
onPremFeatures = currentPlan;
|
||||
instanceType = InstanceType.EnterpriseOnPrem;
|
||||
logger.info(`Instance type: ${InstanceType.EnterpriseOnPrem}`);
|
||||
isValidLicense = true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getPlan = async (orgId: string, projectId?: string) => {
|
||||
try {
|
||||
if (instanceType === InstanceType.Cloud) {
|
||||
const cachedPlan = featureStore.get<TFeatureSet>(FEATURE_CACHE_KEY(orgId, projectId));
|
||||
if (cachedPlan) return cachedPlan;
|
||||
|
||||
const org = await orgDal.findOrgById(orgId);
|
||||
if (!org) throw new BadRequestError({ message: "Org not found" });
|
||||
const {
|
||||
data: { currentPlan }
|
||||
} = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>(
|
||||
`/api/license-server/v1/customers/${org.customerId}/cloud-plan`,
|
||||
{
|
||||
params: {
|
||||
workspaceId: projectId
|
||||
}
|
||||
}
|
||||
);
|
||||
featureStore.set(FEATURE_CACHE_KEY(org.id, projectId), currentPlan);
|
||||
return currentPlan;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return onPremFeatures;
|
||||
}
|
||||
return onPremFeatures;
|
||||
};
|
||||
|
||||
const refreshPlan = async (orgId: string, projectId?: string) => {
|
||||
if (instanceType === InstanceType.Cloud) {
|
||||
featureStore.del(FEATURE_CACHE_KEY(orgId, projectId));
|
||||
await getPlan(orgId, projectId);
|
||||
}
|
||||
};
|
||||
|
||||
const generateOrgCustomerId = async (orgName: string, email: string) => {
|
||||
const {
|
||||
data: { customerId }
|
||||
} = await licenceApi.post("/api/license-server/v1/customers", { email, name: orgName });
|
||||
return customerId;
|
||||
if (instanceType === InstanceType.Cloud) {
|
||||
const {
|
||||
data: { customerId }
|
||||
} = await licenseServerCloudApi.request.post(
|
||||
"/api/license-server/v1/customers",
|
||||
{
|
||||
email,
|
||||
name: orgName
|
||||
},
|
||||
{ timeout: 5000, signal: AbortSignal.timeout(5000) }
|
||||
);
|
||||
return customerId;
|
||||
}
|
||||
};
|
||||
|
||||
const removeOrgCustomer = async (customerId: string) => {
|
||||
await licenceApi.delete(`/api/license-server/v1/customers/${customerId}`);
|
||||
await licenseServerCloudApi.request.delete(`/api/license-server/v1/customers/${customerId}`);
|
||||
};
|
||||
|
||||
const updateSubscriptionOrgMemberCount = async (orgId: string) => {
|
||||
if (instanceType === InstanceType.Cloud) {
|
||||
const org = await orgDal.findOrgById(orgId);
|
||||
if (!org) throw new BadRequestError({ message: "Org not found" });
|
||||
|
||||
const count = await licenseDal.countOfOrgMembers(orgId);
|
||||
if (org?.customerId) {
|
||||
await licenseServerCloudApi.request.patch(
|
||||
`/api/license-server/v1/customers/${org.customerId}/cloud-plan`,
|
||||
{
|
||||
quantity: count
|
||||
}
|
||||
);
|
||||
}
|
||||
featureStore.del(orgId);
|
||||
} else if (instanceType === InstanceType.EnterpriseOnPrem) {
|
||||
const usedSeats = await licenseDal.countOfOrgMembers(null);
|
||||
await licenseServerOnPremApi.request.patch(`/api/license/v1/license`, { usedSeats });
|
||||
}
|
||||
await refreshPlan(orgId);
|
||||
};
|
||||
|
||||
// below all are api calls
|
||||
const getOrgPlansTableByBillCycle = async ({
|
||||
orgId,
|
||||
actor,
|
||||
actorId,
|
||||
billingCycle
|
||||
}: TOrgPlansTableDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
const { data } = await licenseServerCloudApi.request.get(
|
||||
`/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgPlan = async ({ orgId, actor, actorId, projectId }: TOrgPlanDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
const plan = await getPlan(orgId, projectId);
|
||||
return plan;
|
||||
};
|
||||
|
||||
const startOrgTrail = async ({ orgId, actorId, actor, success_url }: TStartOrgTrailDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
data: { url }
|
||||
} = await licenseServerCloudApi.request.post(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/session/trail`,
|
||||
{ success_url }
|
||||
);
|
||||
featureStore.del(FEATURE_CACHE_KEY(orgId));
|
||||
return { url };
|
||||
};
|
||||
|
||||
const getOrgBillingInfo = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
const { data } = await licenseServerCloudApi.request.get(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/billing`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
// returns org current plan feature table
|
||||
const getOrgPlanTable = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
const { data } = await licenseServerCloudApi.request.get(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/cloud-plan/table`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgBillingDetails = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
|
||||
const { data } = await licenseServerCloudApi.request.get(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/billing-details`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateOrgBillingDetails = async ({
|
||||
actorId,
|
||||
actor,
|
||||
orgId,
|
||||
name,
|
||||
email
|
||||
}: TUpdateOrgBillingDetailsDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
const { data } = await licenseServerCloudApi.request.patch(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/billing-details`,
|
||||
{
|
||||
name,
|
||||
email
|
||||
}
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgPmtMethods = async ({ orgId, actor, actorId }: TOrgPmtMethodsDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
data: { pmtMethods }
|
||||
} = await licenseServerCloudApi.request.get(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods`
|
||||
);
|
||||
return pmtMethods;
|
||||
};
|
||||
|
||||
const addOrgPmtMethods = async ({
|
||||
orgId,
|
||||
actor,
|
||||
actorId,
|
||||
success_url,
|
||||
cancel_url
|
||||
}: TAddOrgPmtMethodDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
const {
|
||||
data: { url }
|
||||
} = await licenseServerCloudApi.request.post(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods`,
|
||||
{
|
||||
success_url,
|
||||
cancel_url
|
||||
}
|
||||
);
|
||||
return { url };
|
||||
};
|
||||
|
||||
const delOrgPmtMethods = async ({ actorId, actor, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
|
||||
const { data } = await licenseServerCloudApi.request.delete(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods/${pmtMethodId}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgTaxIds = async ({ orgId, actor, actorId }: TGetOrgTaxIdDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
const {
|
||||
data: { tax_ids: taxIds }
|
||||
} = await licenseServerCloudApi.request.get(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/billing-details/tax-ids`
|
||||
);
|
||||
return taxIds;
|
||||
};
|
||||
|
||||
const addOrgTaxId = async ({ actorId, actor, orgId, type, value }: TAddOrgTaxIdDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
|
||||
const { data } = await licenseServerCloudApi.request.post(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/billing-details/tax-ids`,
|
||||
{
|
||||
type,
|
||||
value
|
||||
}
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const delOrgTaxId = async ({ orgId, actor, actorId, taxId }: TDelOrgTaxIdDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
|
||||
const { data } = await licenseServerCloudApi.request.delete(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/billing-details/tax-ids/${taxId}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const getOrgTaxInvoices = async ({ actorId, actor, orgId }: TOrgInvoiceDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
data: { invoices }
|
||||
} = await licenseServerCloudApi.request.get(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/invoices`
|
||||
);
|
||||
return invoices;
|
||||
};
|
||||
|
||||
const getOrgLicenses = async ({ orgId, actor, actorId }: TOrgLicensesDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
);
|
||||
|
||||
const organization = await orgDal.findOrgById(orgId);
|
||||
if (!organization) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
data: { licenses }
|
||||
} = await licenseServerCloudApi.request.get(
|
||||
`/api/license-server/v1/customers/${organization.customerId}/licenses`
|
||||
);
|
||||
return licenses;
|
||||
};
|
||||
|
||||
return {
|
||||
generateOrgCustomerId,
|
||||
removeOrgCustomer
|
||||
removeOrgCustomer,
|
||||
init,
|
||||
get isValidLicense() {
|
||||
return isValidLicense;
|
||||
},
|
||||
getPlan,
|
||||
updateSubscriptionOrgMemberCount,
|
||||
refreshPlan,
|
||||
getOrgPlan,
|
||||
getOrgPlansTableByBillCycle,
|
||||
startOrgTrail,
|
||||
getOrgBillingInfo,
|
||||
getOrgPlanTable,
|
||||
getOrgBillingDetails,
|
||||
updateOrgBillingDetails,
|
||||
addOrgPmtMethods,
|
||||
delOrgPmtMethods,
|
||||
getOrgPmtMethods,
|
||||
getOrgLicenses,
|
||||
getOrgTaxInvoices,
|
||||
getOrgTaxIds,
|
||||
addOrgTaxId,
|
||||
delOrgTaxId
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { TOrgPermission } from "@app/lib/types";
|
||||
|
||||
export enum InstanceType {
|
||||
OnPrem = "self-hosted",
|
||||
EnterpriseOnPrem = "enterprise-self-hosted",
|
||||
Cloud = "cloud"
|
||||
}
|
||||
|
||||
export type TFeatureSet = {
|
||||
_id: null;
|
||||
slug: null;
|
||||
tier: -1;
|
||||
workspaceLimit: null;
|
||||
workspacesUsed: 0;
|
||||
memberLimit: null;
|
||||
membersUsed: 0;
|
||||
environmentLimit: null;
|
||||
environmentsUsed: 0;
|
||||
secretVersioning: true;
|
||||
pitRecovery: false;
|
||||
ipAllowlisting: false;
|
||||
rbac: false;
|
||||
customRateLimits: false;
|
||||
customAlerts: false;
|
||||
auditLogs: false;
|
||||
auditLogsRetentionDays: 0;
|
||||
samlSSO: false;
|
||||
status: null;
|
||||
trial_end: null;
|
||||
has_used_trial: true;
|
||||
secretApproval: false;
|
||||
secretRotation: true;
|
||||
};
|
||||
|
||||
export type TOrgPlansTableDTO = {
|
||||
billingCycle: string;
|
||||
} & TOrgPermission;
|
||||
|
||||
export type TOrgPlanDTO = {
|
||||
projectId?: string;
|
||||
} & TOrgPermission;
|
||||
|
||||
export type TStartOrgTrailDTO = {
|
||||
success_url: string;
|
||||
} & TOrgPermission;
|
||||
|
||||
export type TGetOrgBillInfoDTO = TOrgPermission;
|
||||
|
||||
export type TOrgPlanTableDTO = TOrgPermission;
|
||||
|
||||
export type TOrgBillingDetailsDTO = TOrgPermission;
|
||||
|
||||
export type TUpdateOrgBillingDetailsDTO = TOrgPermission & {
|
||||
name?: string;
|
||||
email?: string;
|
||||
};
|
||||
|
||||
export type TOrgPmtMethodsDTO = TOrgPermission;
|
||||
|
||||
export type TAddOrgPmtMethodDTO = TOrgPermission & { success_url: string; cancel_url: string };
|
||||
|
||||
export type TDelOrgPmtMethodDTO = TOrgPermission & { pmtMethodId: string };
|
||||
|
||||
export type TGetOrgTaxIdDTO = TOrgPermission;
|
||||
|
||||
export type TAddOrgTaxIdDTO = TOrgPermission & { type: string; value: string };
|
||||
|
||||
export type TDelOrgTaxIdDTO = TOrgPermission & { taxId: string };
|
||||
|
||||
export type TOrgInvoiceDTO = TOrgPermission;
|
||||
|
||||
export type TOrgLicensesDTO = TOrgPermission;
|
||||
|
||||
@@ -20,6 +20,7 @@ import { TOrgBotDalFactory } from "@app/services/org/org-bot-dal";
|
||||
import { TOrgDalFactory } from "@app/services/org/org-dal";
|
||||
import { TUserDalFactory } from "@app/services/user/user-dal";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TSamlConfigDalFactory } from "./saml-config-dal";
|
||||
@@ -40,6 +41,7 @@ type TSamlConfigServiceFactoryDep = {
|
||||
>;
|
||||
orgBotDal: Pick<TOrgBotDalFactory, "findOne">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TSamlConfigServiceFactory = ReturnType<typeof samlConfigServiceFactory>;
|
||||
@@ -49,7 +51,8 @@ export const samlConfigServiceFactory = ({
|
||||
orgBotDal,
|
||||
orgDal,
|
||||
userDal,
|
||||
permissionService
|
||||
permissionService,
|
||||
licenseService
|
||||
}: TSamlConfigServiceFactoryDep) => {
|
||||
const createSamlCfg = async ({
|
||||
cert,
|
||||
@@ -67,7 +70,12 @@ export const samlConfigServiceFactory = ({
|
||||
OrgPermissionSubjects.Sso
|
||||
);
|
||||
|
||||
// TODO(akhilmhdh-pg): licence check
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.samlSSO)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
|
||||
});
|
||||
|
||||
const orgBot = await orgBotDal.findOne({ orgId });
|
||||
if (!orgBot)
|
||||
@@ -123,6 +131,13 @@ export const samlConfigServiceFactory = ({
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.Sso
|
||||
);
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan.samlSSO)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
|
||||
});
|
||||
|
||||
const updateQuery: TSamlConfigsUpdate = { authProvider, isActive };
|
||||
const orgBot = await orgBotDal.findOne({ orgId });
|
||||
if (!orgBot)
|
||||
|
||||
@@ -4,8 +4,10 @@ import Ajv from "ajv";
|
||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
import { TProjectDalFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectEnvDalFactory } from "@app/services/project-env/project-env-dal";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import { TSecretRotationDalFactory } from "./secret-rotation-dal";
|
||||
@@ -22,6 +24,8 @@ import { rotationTemplates } from "./templates";
|
||||
|
||||
type TSecretRotationServiceFactoryDep = {
|
||||
secretRotationDal: TSecretRotationDalFactory;
|
||||
projectDal: Pick<TProjectDalFactory, "findById">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
projectEnvDal: Pick<TProjectEnvDalFactory, "findOne">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
secretRotationQueue: TSecretRotationQueueFactory;
|
||||
@@ -34,7 +38,9 @@ export const secretRotationServiceFactory = ({
|
||||
secretRotationDal,
|
||||
permissionService,
|
||||
projectEnvDal,
|
||||
secretRotationQueue
|
||||
secretRotationQueue,
|
||||
licenseService,
|
||||
projectDal
|
||||
}: TSecretRotationServiceFactoryDep) => {
|
||||
const getProviderTemplates = async ({ actor, actorId, projectId }: TProjectPermission) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||
@@ -68,6 +74,14 @@ export const secretRotationServiceFactory = ({
|
||||
const env = await projectEnvDal.findOne({ slug: environment });
|
||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
||||
|
||||
const project = await projectDal.findById(projectId);
|
||||
const plan = await licenseService.getPlan(project.orgId);
|
||||
if (!plan.secretRotation)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
|
||||
});
|
||||
|
||||
const selectedTemplate = rotationTemplates.find(({ name }) => name === provider);
|
||||
if (!selectedTemplate) throw new BadRequestError({ message: "Provider not found" });
|
||||
const formattedInputs: Record<string, unknown> = {};
|
||||
@@ -149,6 +163,14 @@ export const secretRotationServiceFactory = ({
|
||||
const doc = await secretRotationDal.findById(rotationId);
|
||||
if (!doc) throw new BadRequestError({ message: "Rotation not found" });
|
||||
|
||||
const project = await projectDal.findById(doc.projectId);
|
||||
const plan = await licenseService.getPlan(project.orgId);
|
||||
if (!plan.secretRotation)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
|
||||
});
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
import { TSecretDalFactory } from "@app/services/secret/secret-dal";
|
||||
import { TSecretVersionDalFactory } from "@app/services/secret/secret-version-dal";
|
||||
import { TSecretFolderDalFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||
import { TSecretFolderVersionDalFactory } from "@app/services/secret-folder/secret-folder-version-dal";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import {
|
||||
@@ -34,6 +35,7 @@ type TSecretSnapshotServiceFactoryDep = {
|
||||
"findById" | "findBySecretPath" | "delete" | "insertMany"
|
||||
>;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "isValidLicense">;
|
||||
};
|
||||
|
||||
export type TSecretSnapshotServiceFactory = ReturnType<typeof secretSnapshotServiceFactory>;
|
||||
@@ -46,7 +48,8 @@ export const secretSnapshotServiceFactory = ({
|
||||
snapshotFolderDal,
|
||||
folderDal,
|
||||
secretDal,
|
||||
permissionService
|
||||
permissionService,
|
||||
licenseService
|
||||
}: TSecretSnapshotServiceFactoryDep) => {
|
||||
const projectSecretSnapshotCount = async ({
|
||||
environment,
|
||||
@@ -109,6 +112,9 @@ export const secretSnapshotServiceFactory = ({
|
||||
};
|
||||
|
||||
const performSnapshot = async (folderId: string) => {
|
||||
if (!licenseService.isValidLicense)
|
||||
throw new InternalServerError({ message: "Invalid license" });
|
||||
|
||||
const snapshot = await snapshotDal.transaction(async (tx) => {
|
||||
const folder = await folderDal.findById(folderId, tx);
|
||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||
|
||||
10
backend-pg/src/ee/services/trusted-ip/trusted-ip-dal.ts
Normal file
10
backend-pg/src/ee/services/trusted-ip/trusted-ip-dal.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TTrustedIpDalFactory = ReturnType<typeof trustedIpDalFactory>;
|
||||
|
||||
export const trustedIpDalFactory = (db: TDbClient) => {
|
||||
const trustedIpOrm = ormify(db, TableName.TrustedIps);
|
||||
return trustedIpOrm;
|
||||
};
|
||||
150
backend-pg/src/ee/services/trusted-ip/trusted-ip-service.ts
Normal file
150
backend-pg/src/ee/services/trusted-ip/trusted-ip-service.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
import { TProjectDalFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import { TTrustedIpDalFactory } from "./trusted-ip-dal";
|
||||
import { TCreateIpDTO, TDeleteIpDTO, TUpdateIpDTO } from "./trusted-ip-types";
|
||||
|
||||
type TTrustedIpServiceFactoryDep = {
|
||||
trustedIpDal: TTrustedIpDalFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
projectDal: Pick<TProjectDalFactory, "findById">;
|
||||
};
|
||||
|
||||
export type TTrustedIpServiceFactory = ReturnType<typeof trustedIpServiceFactory>;
|
||||
|
||||
export const trustedIpServiceFactory = ({
|
||||
trustedIpDal,
|
||||
permissionService,
|
||||
licenseService,
|
||||
projectDal
|
||||
}: TTrustedIpServiceFactoryDep) => {
|
||||
const listIpsByProjectId = async ({ projectId, actor, actorId }: TProjectPermission) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.IpAllowList
|
||||
);
|
||||
const trustedIps = await trustedIpDal.find({
|
||||
projectId
|
||||
});
|
||||
return trustedIps;
|
||||
};
|
||||
|
||||
const addProjectIp = async ({
|
||||
projectId,
|
||||
actorId,
|
||||
actor,
|
||||
ipAddress: ip,
|
||||
comment,
|
||||
isActive
|
||||
}: TCreateIpDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.IpAllowList
|
||||
);
|
||||
|
||||
const project = await projectDal.findById(projectId);
|
||||
const plan = await licenseService.getPlan(project.orgId);
|
||||
if (!plan.ipAllowlisting)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range."
|
||||
});
|
||||
|
||||
const isValidIp = isValidIpOrCidr(ip);
|
||||
if (!isValidIp)
|
||||
throw new BadRequestError({
|
||||
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||
});
|
||||
|
||||
const { ipAddress, type, prefix } = extractIPDetails(ip);
|
||||
const trustedIp = await trustedIpDal.create({
|
||||
projectId,
|
||||
ipAddress,
|
||||
type,
|
||||
prefix,
|
||||
isActive,
|
||||
comment
|
||||
});
|
||||
|
||||
return { trustedIp, project }; // for audit log
|
||||
};
|
||||
|
||||
const updateProjectIp = async ({
|
||||
projectId,
|
||||
actorId,
|
||||
actor,
|
||||
ipAddress: ip,
|
||||
comment,
|
||||
trustedIpId
|
||||
}: TUpdateIpDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.IpAllowList
|
||||
);
|
||||
|
||||
const project = await projectDal.findById(projectId);
|
||||
const plan = await licenseService.getPlan(project.orgId);
|
||||
if (!plan.ipAllowlisting)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range."
|
||||
});
|
||||
|
||||
const isValidIp = isValidIpOrCidr(ip);
|
||||
if (!isValidIp)
|
||||
throw new BadRequestError({
|
||||
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||
});
|
||||
|
||||
const { ipAddress, type, prefix } = extractIPDetails(ip);
|
||||
const [trustedIp] = await trustedIpDal.update(
|
||||
{ projectId, id: trustedIpId },
|
||||
{
|
||||
projectId,
|
||||
ipAddress,
|
||||
type,
|
||||
prefix: prefix === undefined ? null : prefix,
|
||||
comment
|
||||
}
|
||||
);
|
||||
|
||||
return { trustedIp, project }; // for audit log
|
||||
};
|
||||
|
||||
const deleteProjectIp = async ({ projectId, actorId, actor, trustedIpId }: TDeleteIpDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.IpAllowList
|
||||
);
|
||||
|
||||
const project = await projectDal.findById(projectId);
|
||||
const plan = await licenseService.getPlan(project.orgId);
|
||||
if (!plan.ipAllowlisting)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range."
|
||||
});
|
||||
|
||||
const [trustedIp] = await trustedIpDal.delete({ projectId, id: trustedIpId });
|
||||
|
||||
return { trustedIp, project }; // for audit log
|
||||
};
|
||||
return {
|
||||
listIpsByProjectId,
|
||||
addProjectIp,
|
||||
updateProjectIp,
|
||||
deleteProjectIp
|
||||
};
|
||||
};
|
||||
17
backend-pg/src/ee/services/trusted-ip/trusted-ip-types.ts
Normal file
17
backend-pg/src/ee/services/trusted-ip/trusted-ip-types.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
export type TCreateIpDTO = TProjectPermission & {
|
||||
comment: string;
|
||||
isActive?: boolean;
|
||||
ipAddress: string;
|
||||
};
|
||||
|
||||
export type TUpdateIpDTO = TProjectPermission & {
|
||||
trustedIpId: string;
|
||||
ipAddress: string;
|
||||
comment: string;
|
||||
};
|
||||
|
||||
export type TDeleteIpDTO = TProjectPermission & {
|
||||
trustedIpId: string;
|
||||
};
|
||||
@@ -83,8 +83,9 @@ const envSchema = z
|
||||
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
|
||||
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
|
||||
// LICENCE
|
||||
LICENCE_SERVER_URL: zpStr(z.string().optional()),
|
||||
LICENCE_SERVER_KEY: zpStr(z.string().optional())
|
||||
LICENSE_SERVER_URL: zpStr(z.string().optional()),
|
||||
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
|
||||
LICENSE_KEY: zpStr(z.string().optional())
|
||||
})
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
|
||||
@@ -11,6 +11,18 @@ export class DatabaseError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export class InternalServerError extends Error {
|
||||
name: string;
|
||||
|
||||
error: unknown;
|
||||
|
||||
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) {
|
||||
super(message || "Something went wrong");
|
||||
this.name = name || "InternalServerError";
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
export class UnauthorizedError extends Error {
|
||||
name: string;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import fp from "fastify-plugin";
|
||||
|
||||
import { jsonSchemaTransform } from "./fastify-zod";
|
||||
|
||||
// TODO(akhilmhdh-pg): change the localhost port later
|
||||
export const fastifySwagger = fp(async (fastify) => {
|
||||
await fastify.register(swagger, {
|
||||
transform: jsonSchemaTransform,
|
||||
@@ -16,7 +15,7 @@ export const fastifySwagger = fp(async (fastify) => {
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: "http://localhost:4000",
|
||||
url: "http://localhost:8080",
|
||||
description: "Local server"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,6 +5,8 @@ import { registerV1EERoutes } from "@app/ee/routes/v1";
|
||||
import { auditLogDalFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
||||
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
||||
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||
import { licenseDalFactory } from "@app/ee/services/license/license-dal";
|
||||
import { licenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { permissionDalFactory } from "@app/ee/services/permission/permission-dal";
|
||||
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { samlConfigDalFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
||||
@@ -28,6 +30,8 @@ import { secretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/s
|
||||
import { snapshotDalFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
||||
import { snapshotFolderDalFactory } from "@app/ee/services/secret-snapshot/snapshot-folder-dal";
|
||||
import { snapshotSecretDalFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-dal";
|
||||
import { trustedIpDalFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal";
|
||||
import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { TQueueServiceFactory } from "@app/queue";
|
||||
import { apiKeyDalFactory } from "@app/services/api-key/api-key-dal";
|
||||
@@ -150,6 +154,7 @@ export const registerRoutes = async (
|
||||
const identityUaClientSecretDal = identityUaClientSecretDalFactory(db);
|
||||
|
||||
const auditLogDal = auditLogDalFactory(db);
|
||||
const trustedIpDal = trustedIpDalFactory(db);
|
||||
|
||||
// ee db layer ops
|
||||
const permissionDal = permissionDalFactory(db);
|
||||
@@ -168,6 +173,7 @@ export const registerRoutes = async (
|
||||
const gitAppInstallSessionDal = gitAppInstallSessionDalFactory(db);
|
||||
const gitAppOrgDal = gitAppDalFactory(db);
|
||||
const secretScanningDal = secretScanningDalFactory(db);
|
||||
const licenseDal = licenseDalFactory(db);
|
||||
|
||||
const permissionService = permissionServiceFactory({
|
||||
permissionDal,
|
||||
@@ -175,7 +181,19 @@ export const registerRoutes = async (
|
||||
projectRoleDal,
|
||||
serviceTokenDal
|
||||
});
|
||||
const auditLogQueue = auditLogQueueServiceFactory({ auditLogDal, queueService });
|
||||
const licenseService = licenseServiceFactory({ permissionService, orgDal, licenseDal });
|
||||
const trustedIpService = trustedIpServiceFactory({
|
||||
licenseService,
|
||||
projectDal,
|
||||
trustedIpDal,
|
||||
permissionService
|
||||
});
|
||||
const auditLogQueue = auditLogQueueServiceFactory({
|
||||
auditLogDal,
|
||||
queueService,
|
||||
projectDal,
|
||||
licenseService
|
||||
});
|
||||
const auditLogService = auditLogServiceFactory({ auditLogDal, permissionService, auditLogQueue });
|
||||
const sapService = secretApprovalPolicyServiceFactory({
|
||||
projectMembershipDal,
|
||||
@@ -189,7 +207,8 @@ export const registerRoutes = async (
|
||||
orgBotDal,
|
||||
orgDal,
|
||||
userDal,
|
||||
samlConfigDal
|
||||
samlConfigDal,
|
||||
licenseService
|
||||
});
|
||||
|
||||
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal, userDal });
|
||||
@@ -202,6 +221,8 @@ export const registerRoutes = async (
|
||||
userDal
|
||||
});
|
||||
const orgService = orgServiceFactory({
|
||||
licenseService,
|
||||
samlConfigDal,
|
||||
orgRoleDal,
|
||||
permissionService,
|
||||
orgDal,
|
||||
@@ -217,7 +238,8 @@ export const registerRoutes = async (
|
||||
authDal,
|
||||
userDal,
|
||||
orgDal,
|
||||
orgService
|
||||
orgService,
|
||||
licenseService
|
||||
});
|
||||
const orgRoleService = orgRoleServiceFactory({ permissionService, orgRoleDal });
|
||||
const superAdminService = superAdminServiceFactory({
|
||||
@@ -247,7 +269,8 @@ export const registerRoutes = async (
|
||||
secretBlindIndexDal,
|
||||
projectEnvDal,
|
||||
projectMembershipDal,
|
||||
folderDal
|
||||
folderDal,
|
||||
licenseService
|
||||
});
|
||||
const projectMembershipService = projectMembershipServiceFactory({
|
||||
projectMembershipDal,
|
||||
@@ -257,9 +280,15 @@ export const registerRoutes = async (
|
||||
userDal,
|
||||
smtpService,
|
||||
projectKeyDal,
|
||||
projectRoleDal
|
||||
projectRoleDal,
|
||||
licenseService
|
||||
});
|
||||
const projectEnvService = projectEnvServiceFactory({
|
||||
permissionService,
|
||||
projectEnvDal,
|
||||
licenseService,
|
||||
projectDal
|
||||
});
|
||||
const projectEnvService = projectEnvServiceFactory({ permissionService, projectEnvDal });
|
||||
const projectKeyService = projectKeyServiceFactory({
|
||||
permissionService,
|
||||
projectKeyDal,
|
||||
@@ -275,7 +304,8 @@ export const registerRoutes = async (
|
||||
snapshotSecretDal,
|
||||
secretVersionDal,
|
||||
folderVersionDal,
|
||||
permissionService
|
||||
permissionService,
|
||||
licenseService
|
||||
});
|
||||
const webhookService = webhookServiceFactory({
|
||||
permissionService,
|
||||
@@ -350,7 +380,9 @@ export const registerRoutes = async (
|
||||
permissionService,
|
||||
projectEnvDal,
|
||||
secretRotationDal,
|
||||
secretRotationQueue
|
||||
secretRotationQueue,
|
||||
projectDal,
|
||||
licenseService
|
||||
});
|
||||
|
||||
const integrationService = integrationServiceFactory({
|
||||
@@ -383,10 +415,13 @@ export const registerRoutes = async (
|
||||
identityDal,
|
||||
identityAccessTokenDal,
|
||||
identityUaClientSecretDal,
|
||||
identityUaDal
|
||||
identityUaDal,
|
||||
licenseService
|
||||
});
|
||||
|
||||
await superAdminService.initServerCfg();
|
||||
// setup the communication with license key server
|
||||
await licenseService.init();
|
||||
// inject all services
|
||||
server.decorate<FastifyZodProvider["services"]>("services", {
|
||||
login: loginService,
|
||||
@@ -423,7 +458,9 @@ export const registerRoutes = async (
|
||||
snapshot: snapshotService,
|
||||
saml: samlService,
|
||||
auditLog: auditLogService,
|
||||
secretScanning: secretScanningService
|
||||
secretScanning: secretScanningService,
|
||||
license: licenseService,
|
||||
trustedIp: trustedIpService
|
||||
});
|
||||
|
||||
server.decorate<FastifyZodProvider["store"]>("store", {
|
||||
|
||||
@@ -39,7 +39,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { identityUa, accessToken, identityAccessToken, validClientSecretInfo } =
|
||||
await server.services.identityUa.login(req.body.clientId, req.body.clientSecret);
|
||||
await server.services.identityUa.login(
|
||||
req.body.clientId,
|
||||
req.body.clientSecret,
|
||||
req.realIp
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
|
||||
@@ -24,10 +24,10 @@ import { registerWebhookRouter } from "./webhook-router";
|
||||
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||
await server.register(registerSsoRouter, { prefix: "/sso" });
|
||||
await server.register(
|
||||
async (authServer) => {
|
||||
await authServer.register(registerAuthRoutes);
|
||||
await authServer.register(registerIdentityUaRouter);
|
||||
await authServer.register(registerIdentityAccessTokenRouter);
|
||||
async (authRouter) => {
|
||||
await authRouter.register(registerAuthRoutes);
|
||||
await authRouter.register(registerIdentityUaRouter);
|
||||
await authRouter.register(registerIdentityAccessTokenRouter);
|
||||
},
|
||||
{ prefix: "/auth" }
|
||||
);
|
||||
@@ -41,12 +41,12 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||
await server.register(registerSecretFolderRouter, { prefix: "/folders" });
|
||||
|
||||
await server.register(
|
||||
async (projectServer) => {
|
||||
await projectServer.register(registerProjectRouter);
|
||||
await projectServer.register(registerProjectEnvRouter);
|
||||
await projectServer.register(registerProjectKeyRouter);
|
||||
await projectServer.register(registerProjectMembershipRouter);
|
||||
await projectServer.register(registerSecretTagRouter);
|
||||
async (projectRouter) => {
|
||||
await projectRouter.register(registerProjectRouter);
|
||||
await projectRouter.register(registerProjectEnvRouter);
|
||||
await projectRouter.register(registerProjectKeyRouter);
|
||||
await projectRouter.register(registerProjectMembershipRouter);
|
||||
await projectRouter.register(registerSecretTagRouter);
|
||||
},
|
||||
{ prefix: "/workspace" }
|
||||
);
|
||||
|
||||
@@ -84,8 +84,6 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(akhilmhdh-pg): missing my-workspace list
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/:organizationId/name",
|
||||
@@ -161,7 +159,6 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
method: "DELETE",
|
||||
url: "/:organizationId/incidentContactOrg/:incidentContactId",
|
||||
schema: {
|
||||
// TODO(akhilmhdh-pg): change accept id instead of email
|
||||
params: z.object({ organizationId: z.string().trim(), incidentContactId: z.string().trim() }),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
||||
@@ -88,7 +88,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
if (req.auth.actor !== ActorType.USER) return;
|
||||
|
||||
|
||||
const membership = await server.services.org.deleteOrgMembership({
|
||||
userId: req.permission.id,
|
||||
orgId: req.params.organizationId,
|
||||
@@ -117,6 +117,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
const organization = await server.services.org.createOrganization(
|
||||
req.permission.id,
|
||||
req.auth.user.email,
|
||||
req.body.name
|
||||
);
|
||||
return { organization };
|
||||
|
||||
@@ -11,27 +11,45 @@ export type TTokenDalConfig = {};
|
||||
|
||||
export type TTokenDalFactory = ReturnType<typeof tokenDalFactory>;
|
||||
|
||||
// TODO(akhilmhdh-pg): wrap all with database error
|
||||
export const tokenDalFactory = (db: TDbClient) => {
|
||||
const authOrm = ormify(db, TableName.AuthTokens);
|
||||
|
||||
const findOneTokenSession = async (
|
||||
filter: Partial<TAuthTokenSessions>
|
||||
): Promise<TAuthTokenSessions | undefined> =>
|
||||
db(TableName.AuthTokenSession).where(filter).first();
|
||||
): Promise<TAuthTokenSessions | undefined> => {
|
||||
try {
|
||||
const doc = await db(TableName.AuthTokenSession).where(filter).first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindOneTokenSession" });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteTokenForUser = async ({
|
||||
userId,
|
||||
type,
|
||||
orgId
|
||||
}: TDeleteTokenForUserDalDTO): Promise<TAuthTokens[] | undefined> =>
|
||||
db(TableName.AuthTokens).where({ userId, type, orgId }).delete().returning("*");
|
||||
}: TDeleteTokenForUserDalDTO): Promise<TAuthTokens[] | undefined> => {
|
||||
try {
|
||||
const doc = await db(TableName.AuthTokens)
|
||||
.where({ userId, type, orgId })
|
||||
.delete()
|
||||
.returning("*");
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "DeleteTokenForUser" });
|
||||
}
|
||||
};
|
||||
|
||||
const decrementTriesField = async ({
|
||||
userId,
|
||||
type
|
||||
}: TDeleteTokenForUserDalDTO): Promise<void> => {
|
||||
await db(TableName.AuthTokens).where({ userId, type }).decrement("triesLeft", 1);
|
||||
try {
|
||||
await db(TableName.AuthTokens).where({ userId, type }).decrement("triesLeft", 1);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "DecrementTriesField" });
|
||||
}
|
||||
};
|
||||
|
||||
const findTokenSessions = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
|
||||
@@ -48,29 +66,37 @@ export const tokenDalFactory = (db: TDbClient) => {
|
||||
ip: string,
|
||||
userAgent: string
|
||||
): Promise<TAuthTokenSessions | undefined> => {
|
||||
const [session] = await db(TableName.AuthTokenSession)
|
||||
.insert({
|
||||
userId,
|
||||
ip,
|
||||
userAgent,
|
||||
accessVersion: 1,
|
||||
refreshVersion: 1,
|
||||
lastUsed: new Date()
|
||||
})
|
||||
.returning("*");
|
||||
return session;
|
||||
try {
|
||||
const [session] = await db(TableName.AuthTokenSession)
|
||||
.insert({
|
||||
userId,
|
||||
ip,
|
||||
userAgent,
|
||||
accessVersion: 1,
|
||||
refreshVersion: 1,
|
||||
lastUsed: new Date()
|
||||
})
|
||||
.returning("*");
|
||||
return session;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "InsertTokenSession" });
|
||||
}
|
||||
};
|
||||
|
||||
const incrementTokenSessionVersion = async (
|
||||
userId: string,
|
||||
sessionId: string
|
||||
): Promise<TAuthTokenSessions | undefined> => {
|
||||
const [session] = await db(TableName.AuthTokenSession)
|
||||
.where({ userId, id: sessionId })
|
||||
.increment("accessVersion", 1)
|
||||
.increment("refreshVersion", 1)
|
||||
.returning("*");
|
||||
return session;
|
||||
try {
|
||||
const [session] = await db(TableName.AuthTokenSession)
|
||||
.where({ userId, id: sessionId })
|
||||
.increment("accessVersion", 1)
|
||||
.increment("refreshVersion", 1)
|
||||
.returning("*");
|
||||
return session;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "IncrementTokenSessionVersion" });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteTokenSession = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
import { OrgMembershipStatus } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { isDisposableEmail } from "@app/lib/validator";
|
||||
@@ -22,6 +23,7 @@ type TAuthSignupDep = {
|
||||
orgDal: TOrgDalFactory;
|
||||
tokenService: TAuthTokenServiceFactory;
|
||||
smtpService: TSmtpService;
|
||||
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
||||
};
|
||||
|
||||
export type TAuthSignupFactory = ReturnType<typeof authSignupServiceFactory>;
|
||||
@@ -31,7 +33,8 @@ export const authSignupServiceFactory = ({
|
||||
tokenService,
|
||||
smtpService,
|
||||
orgService,
|
||||
orgDal
|
||||
orgDal,
|
||||
licenseService
|
||||
}: TAuthSignupDep) => {
|
||||
// first step of signup. create user and send email
|
||||
const beginEmailSignupProcess = async (email: string) => {
|
||||
@@ -143,13 +146,17 @@ export const authSignupServiceFactory = ({
|
||||
);
|
||||
|
||||
if (!hasSamlEnabled) {
|
||||
await orgService.createOrganization(user.id, organizationName);
|
||||
await orgService.createOrganization(user.id, user.email, organizationName);
|
||||
}
|
||||
|
||||
await orgDal.updateMembership(
|
||||
const updatedMembersips = await orgDal.updateMembership(
|
||||
{ inviteEmail: email, status: OrgMembershipStatus.Invited },
|
||||
{ userId: user.id, status: OrgMembershipStatus.Accepted }
|
||||
);
|
||||
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
|
||||
await Promise.allSettled(
|
||||
uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId))
|
||||
);
|
||||
|
||||
const tokenSession = await tokenService.getUserTokenSession({
|
||||
userAgent,
|
||||
@@ -238,11 +245,16 @@ export const authSignupServiceFactory = ({
|
||||
tx
|
||||
);
|
||||
|
||||
await orgDal.updateMembership(
|
||||
const updatedMembersips = await orgDal.updateMembership(
|
||||
{ inviteEmail: email, status: OrgMembershipStatus.Invited },
|
||||
{ userId: us.id, status: OrgMembershipStatus.Accepted },
|
||||
tx
|
||||
);
|
||||
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
|
||||
await Promise.allSettled(
|
||||
uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId))
|
||||
);
|
||||
|
||||
return { info: us, key: userEncKey };
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import bcrypt from "bcrypt";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
import { IdentityAuthMethod } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects
|
||||
@@ -13,7 +14,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||
import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr,TIp } from "@app/lib/ip";
|
||||
|
||||
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||
import { TIdentityDalFactory } from "../identity/identity-dal";
|
||||
@@ -38,6 +39,7 @@ type TIdentityUaServiceFactoryDep = {
|
||||
identityOrgMembershipDal: TIdentityOrgDalFactory;
|
||||
identityDal: Pick<TIdentityDalFactory, "updateById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TIdentityUaServiceFactory = ReturnType<typeof identityUaServiceFactory>;
|
||||
@@ -48,13 +50,17 @@ export const identityUaServiceFactory = ({
|
||||
identityAccessTokenDal,
|
||||
identityOrgMembershipDal,
|
||||
identityDal,
|
||||
permissionService
|
||||
permissionService,
|
||||
licenseService
|
||||
}: TIdentityUaServiceFactoryDep) => {
|
||||
const login = async (clientId: string, clientSecret: string) => {
|
||||
const login = async (clientId: string, clientSecret: string, ip: string) => {
|
||||
const identityUa = await identityUaDal.findOne({ clientId });
|
||||
if (!identityUa) throw new UnauthorizedError();
|
||||
|
||||
// TODO(akhilmhdh-pg): add ip checking
|
||||
checkIPAgainstBlocklist({
|
||||
ipAddress: ip,
|
||||
trustedIps: identityUa.clientSecretTrustedIps as TIp[]
|
||||
});
|
||||
const clientSecrtInfo = await identityUaClientSecretDal.find({
|
||||
identityUAId: identityUa.id,
|
||||
isClientSecretRevoked: false
|
||||
@@ -166,10 +172,11 @@ export const identityUaServiceFactory = ({
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||
const reformattedClientSecretTrustedIps = clientSecretTrustedIps.map(
|
||||
(clientSecretTrustedIp) => {
|
||||
// TODO(akhilmhdh-pg): add licence server here
|
||||
if (/* !plan.ipAllowlisting && */ clientSecretTrustedIp.ipAddress !== "0.0.0.0/0")
|
||||
if (!plan.ipAllowlisting && clientSecretTrustedIp.ipAddress !== "0.0.0.0/0")
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
|
||||
@@ -182,8 +189,7 @@ export const identityUaServiceFactory = ({
|
||||
}
|
||||
);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
// TODO(akhilmhdh-pg): add licence server here
|
||||
if (/* !plan.ipAllowlisting && */ accessTokenTrustedIp.ipAddress !== "0.0.0.0/0")
|
||||
if (!plan.ipAllowlisting && accessTokenTrustedIp.ipAddress !== "0.0.0.0/0")
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
|
||||
@@ -256,10 +262,11 @@ export const identityUaServiceFactory = ({
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.Identity
|
||||
);
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||
const reformattedClientSecretTrustedIps = clientSecretTrustedIps?.map(
|
||||
(clientSecretTrustedIp) => {
|
||||
// TODO(akhilmhdh-pg): add licence server here
|
||||
if (/* !plan.ipAllowlisting && */ clientSecretTrustedIp.ipAddress !== "0.0.0.0/0")
|
||||
if (!plan.ipAllowlisting && clientSecretTrustedIp.ipAddress !== "0.0.0.0/0")
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
|
||||
@@ -272,8 +279,7 @@ export const identityUaServiceFactory = ({
|
||||
}
|
||||
);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
// TODO(akhilmhdh-pg): add licence server here
|
||||
if (/* !plan.ipAllowlisting && */ accessTokenTrustedIp.ipAddress !== "0.0.0.0/0")
|
||||
if (!plan.ipAllowlisting && accessTokenTrustedIp.ipAddress !== "0.0.0.0/0")
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
|
||||
|
||||
@@ -56,7 +56,7 @@ export const identityServiceFactory = ({
|
||||
return newIdentity;
|
||||
});
|
||||
|
||||
// TODO(akhilmhdh-pg): add audit log here
|
||||
|
||||
return identity;
|
||||
};
|
||||
|
||||
|
||||
@@ -994,7 +994,6 @@ export const integrationAuthServiceFactory = ({
|
||||
});
|
||||
|
||||
return delIntegrationAuth;
|
||||
// TODO(akhilmhdh-pg): add audit log
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -90,7 +90,7 @@ export const integrationServiceFactory = ({
|
||||
integration: integrationAuth.integration
|
||||
});
|
||||
|
||||
// TODO(akhilmhdh-pg): audit log
|
||||
|
||||
return { integration, integrationAuth };
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
|
||||
import {
|
||||
TableName,
|
||||
TOrganizations,
|
||||
TOrganizationsInsert,
|
||||
TOrgMemberships,
|
||||
TOrgMembershipsInsert,
|
||||
TOrgMembershipsUpdate
|
||||
@@ -73,11 +74,9 @@ export const orgDalFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const create = async ({ name }: { name: string }, tx?: Knex) => {
|
||||
const create = async (dto: TOrganizationsInsert, tx?: Knex) => {
|
||||
try {
|
||||
const [organization] = await (tx || db)(TableName.Organization)
|
||||
.insert({ name })
|
||||
.returning("*");
|
||||
const [organization] = await (tx || db)(TableName.Organization).insert(dto).returning("*");
|
||||
return organization;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Create organization" });
|
||||
|
||||
@@ -2,11 +2,13 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects
|
||||
} from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { TSamlConfigDalFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
|
||||
import { generateSymmetricKey, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
@@ -35,9 +37,14 @@ type TOrgServiceFactoryDep = {
|
||||
orgRoleDal: TOrgRoleDalFactory;
|
||||
userDal: TUserDalFactory;
|
||||
incidentContactDal: TIncidentContactsDalFactory;
|
||||
samlConfigDal: Pick<TSamlConfigDalFactory, "findOne">;
|
||||
smtpService: TSmtpService;
|
||||
tokenService: TAuthTokenServiceFactory;
|
||||
permissionService: TPermissionServiceFactory;
|
||||
licenseService: Pick<
|
||||
TLicenseServiceFactory,
|
||||
"getPlan" | "updateSubscriptionOrgMemberCount" | "generateOrgCustomerId" | "removeOrgCustomer"
|
||||
>;
|
||||
};
|
||||
|
||||
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
||||
@@ -50,7 +57,9 @@ export const orgServiceFactory = ({
|
||||
permissionService,
|
||||
smtpService,
|
||||
tokenService,
|
||||
orgBotDal
|
||||
orgBotDal,
|
||||
licenseService,
|
||||
samlConfigDal
|
||||
}: TOrgServiceFactoryDep) => {
|
||||
/*
|
||||
* Get organization details by the organization id
|
||||
@@ -99,7 +108,7 @@ export const orgServiceFactory = ({
|
||||
/*
|
||||
* Create organization
|
||||
* */
|
||||
const createOrganization = async (userId: string, orgName: string) => {
|
||||
const createOrganization = async (userId: string, userEmail: string, orgName: string) => {
|
||||
const { privateKey, publicKey } = generateAsymmetricKeyPair();
|
||||
const key = generateSymmetricKey();
|
||||
const {
|
||||
@@ -117,8 +126,9 @@ export const orgServiceFactory = ({
|
||||
algorithm: symmetricKeyAlgorithm
|
||||
} = infisicalSymmetricEncypt(key);
|
||||
|
||||
const customerId = await licenseService.generateOrgCustomerId(orgName, userEmail);
|
||||
const organization = await orgDal.transaction(async (tx) => {
|
||||
const org = await orgDal.create({ name: orgName }, tx);
|
||||
const org = await orgDal.create({ name: orgName, customerId }, tx);
|
||||
await orgDal.createMembership(
|
||||
{
|
||||
userId,
|
||||
@@ -161,6 +171,9 @@ export const orgServiceFactory = ({
|
||||
throw new UnauthorizedError({ name: "Delete org by id", message: "Not an admin" });
|
||||
|
||||
const organization = await orgDal.deleteById(orgId);
|
||||
if (organization.customerId) {
|
||||
await licenseService.removeOrgCustomer(organization.customerId);
|
||||
}
|
||||
return organization;
|
||||
};
|
||||
/*
|
||||
@@ -184,6 +197,14 @@ export const orgServiceFactory = ({
|
||||
const customRole = await orgRoleDal.findOne({ slug: role, orgId });
|
||||
if (!customRole)
|
||||
throw new BadRequestError({ name: "Update membership", message: "Role not found" });
|
||||
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (!plan?.rbac)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
|
||||
});
|
||||
|
||||
const [membership] = await orgDal.updateMembership(
|
||||
{ id: membershipId, orgId },
|
||||
{
|
||||
@@ -210,7 +231,21 @@ export const orgServiceFactory = ({
|
||||
OrgPermissionSubjects.Member
|
||||
);
|
||||
|
||||
// TODO(akhilmhdh-pg): SAML SSO check and licence check limit org members
|
||||
const samlCfg = await samlConfigDal.findOne({ orgId });
|
||||
if (samlCfg && samlCfg.isActive) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to invite member due to SAML SSO configured for organization"
|
||||
});
|
||||
}
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (plan.memberLimit !== null && plan.membersUsed >= plan.memberLimit) {
|
||||
// case: limit imposed on number of members allowed
|
||||
// case: number of members used exceeds the number of members allowed
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
||||
});
|
||||
}
|
||||
const invitee = await orgDal.transaction(async (tx) => {
|
||||
const inviteeUser = await userDal.findUserByEmail(inviteeEmail, tx);
|
||||
if (inviteeUser) {
|
||||
@@ -284,6 +319,7 @@ export const orgServiceFactory = ({
|
||||
}
|
||||
});
|
||||
|
||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||
if (!appCfg.isSmtpConfigured) {
|
||||
return `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${inviteeEmail}&organization_id=${org?.id}`;
|
||||
}
|
||||
@@ -323,7 +359,7 @@ export const orgServiceFactory = ({
|
||||
orgId,
|
||||
status: OrgMembershipStatus.Accepted
|
||||
});
|
||||
// TODO(akhilmhdh-pg): update org licence subscription
|
||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||
return { user };
|
||||
}
|
||||
|
||||
@@ -350,6 +386,8 @@ export const orgServiceFactory = ({
|
||||
);
|
||||
|
||||
const membership = await orgDal.deleteMembershipById(membershipId, orgId);
|
||||
|
||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||
return membership;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
@@ -7,19 +8,24 @@ import {
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
|
||||
import { TProjectDalFactory } from "../project/project-dal";
|
||||
import { TProjectEnvDalFactory } from "./project-env-dal";
|
||||
import { TCreateEnvDTO, TDeleteEnvDTO, TUpdateEnvDTO } from "./project-env-types";
|
||||
|
||||
type TProjectEnvServiceFactoryDep = {
|
||||
projectEnvDal: TProjectEnvDalFactory;
|
||||
projectDal: Pick<TProjectDalFactory, "findById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TProjectEnvServiceFactory = ReturnType<typeof projectEnvServiceFactory>;
|
||||
|
||||
export const projectEnvServiceFactory = ({
|
||||
projectEnvDal,
|
||||
permissionService
|
||||
permissionService,
|
||||
licenseService,
|
||||
projectDal
|
||||
}: TProjectEnvServiceFactoryDep) => {
|
||||
const createEnvironment = async ({ projectId, actorId, actor, name, slug }: TCreateEnvDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||
@@ -28,14 +34,25 @@ export const projectEnvServiceFactory = ({
|
||||
ProjectPermissionSub.Environments
|
||||
);
|
||||
|
||||
// TODO(akhilmhdh-pg): add licence service here
|
||||
const existingEnv = await projectEnvDal.findOne({ slug });
|
||||
const envs = await projectEnvDal.find({ projectId });
|
||||
const existingEnv = envs.find(({ slug: envSlug }) => envSlug === slug);
|
||||
if (existingEnv)
|
||||
throw new BadRequestError({
|
||||
message: "Environment with slug already exist",
|
||||
name: "Create envv"
|
||||
});
|
||||
|
||||
const project = await projectDal.findById(projectId);
|
||||
const plan = await licenseService.getPlan(project.orgId);
|
||||
if (plan.environmentLimit !== null && envs.length >= plan.environmentLimit) {
|
||||
// case: limit imposed on number of environments allowed
|
||||
// case: number of environments used exceeds the number of environments allowed
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to create environment due to environment limit reached. Upgrade plan to create more environments."
|
||||
});
|
||||
}
|
||||
|
||||
const env = await projectEnvDal.transaction(async (tx) => {
|
||||
const lastPos = await projectEnvDal.findLastEnvPosition(projectId, tx);
|
||||
const doc = await projectEnvDal.create({ slug, name, projectId, position: lastPos + 1 }, tx);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { OrgMembershipStatus, ProjectMembershipRole } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
@@ -34,6 +35,7 @@ type TProjectMembershipServiceFactoryDep = {
|
||||
orgDal: Pick<TOrgDalFactory, "findMembership">;
|
||||
projectDal: Pick<TProjectDalFactory, "findById">;
|
||||
projectKeyDal: Pick<TProjectKeyDalFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TProjectMembershipServiceFactory = ReturnType<typeof projectMembershipServiceFactory>;
|
||||
@@ -46,7 +48,8 @@ export const projectMembershipServiceFactory = ({
|
||||
orgDal,
|
||||
userDal,
|
||||
projectDal,
|
||||
projectKeyDal
|
||||
projectKeyDal,
|
||||
licenseService
|
||||
}: TProjectMembershipServiceFactoryDep) => {
|
||||
const getProjectMemberships = async ({ actorId, actor, projectId }: TGetProjectMembershipDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||
@@ -120,7 +123,6 @@ export const projectMembershipServiceFactory = ({
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(akhilmhdh-pg): Audit log
|
||||
return { invitee, latestKey };
|
||||
};
|
||||
|
||||
@@ -209,6 +211,14 @@ export const projectMembershipServiceFactory = ({
|
||||
const customRole = await projectRoleDal.findOne({ slug: role, projectId });
|
||||
if (!customRole)
|
||||
throw new BadRequestError({ name: "Update project membership", message: "Role not found" });
|
||||
const project = await projectDal.findById(customRole.projectId);
|
||||
const plan = await licenseService.getPlan(project.orgId);
|
||||
if (!plan?.rbac)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
|
||||
});
|
||||
|
||||
const [membership] = await projectMembershipDal.update(
|
||||
{ id: membershipId, projectId },
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { createSecretBlindIndex } from "@app/lib/crypto";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
|
||||
import { TProjectEnvDalFactory } from "../project-env/project-env-dal";
|
||||
import { TProjectMembershipDalFactory } from "../project-membership/project-membership-dal";
|
||||
@@ -33,6 +35,7 @@ type TProjectServiceFactoryDep = {
|
||||
projectMembershipDal: Pick<TProjectMembershipDalFactory, "create">;
|
||||
secretBlindIndexDal: Pick<TSecretBlindIndexDalFactory, "create">;
|
||||
permissionService: TPermissionServiceFactory;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TProjectServiceFactory = ReturnType<typeof projectServiceFactory>;
|
||||
@@ -43,7 +46,8 @@ export const projectServiceFactory = ({
|
||||
folderDal,
|
||||
secretBlindIndexDal,
|
||||
projectMembershipDal,
|
||||
projectEnvDal
|
||||
projectEnvDal,
|
||||
licenseService
|
||||
}: TProjectServiceFactoryDep) => {
|
||||
/*
|
||||
* Create workspace. Make user the admin
|
||||
@@ -57,7 +61,17 @@ export const projectServiceFactory = ({
|
||||
|
||||
const appCfg = getConfig();
|
||||
const blindIndex = createSecretBlindIndex(appCfg.ROOT_ENCRYPTION_KEY, appCfg.ENCRYPTION_KEY);
|
||||
// TODO(backend-pg): licence server
|
||||
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
if (plan.workspaceLimit !== null && plan.workspacesUsed >= plan.workspaceLimit) {
|
||||
// case: limit imposed on number of workspaces allowed
|
||||
// case: number of workspaces used exceeds the number of workspaces allowed
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces."
|
||||
});
|
||||
}
|
||||
|
||||
const newProject = projectDal.transaction(async (tx) => {
|
||||
const project = await projectDal.create({ name: workspaceName, orgId }, tx);
|
||||
// set user as admin member for proeject
|
||||
|
||||
@@ -82,7 +82,6 @@ export const serviceTokenServiceFactory = ({
|
||||
});
|
||||
|
||||
const token = `st.${serviceToken.id.toString()}.${secret}`;
|
||||
// TODO(akhilmhdh-pg): audit log
|
||||
|
||||
return { token, serviceToken };
|
||||
};
|
||||
|
||||
@@ -84,7 +84,6 @@ export const webhookServiceFactory = ({
|
||||
}
|
||||
|
||||
const webhook = await webhookDal.create(insertDoc);
|
||||
// TODO(akhilmhdh-pg): add audit log
|
||||
return { ...webhook, projectId, environment: env };
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export type TrustedIp = {
|
||||
id: string;
|
||||
workspace: string;
|
||||
projectId: string;
|
||||
ipAddress: string;
|
||||
type: "ipv4" | "ipv6";
|
||||
isActive: boolean;
|
||||
|
||||
@@ -172,7 +172,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
|
||||
{!isLoading &&
|
||||
filterdUser?.map(
|
||||
({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status }) => {
|
||||
const name = u ? `${u.firstName} ${u.lastName}` : "-";
|
||||
const name = u && u.firstName ? `${u.firstName} ${u.lastName}` : "-";
|
||||
const email = u?.email || inviteEmail;
|
||||
return (
|
||||
<Tr key={`org-membership-${orgMembershipId}`} className="w-full">
|
||||
|
||||
Reference in New Issue
Block a user