mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 19:28:09 +00:00
feat: completed license server integration
This commit is contained in:
Generated
+22
-3
@@ -22,6 +22,7 @@
|
|||||||
"@fastify/swagger-ui": "^1.10.1",
|
"@fastify/swagger-ui": "^1.10.1",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"argon2": "^0.31.2",
|
"argon2": "^0.31.2",
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"mysql2": "^3.6.5",
|
"mysql2": "^3.6.5",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
|
"node-cache": "^5.1.2",
|
||||||
"nodemailer": "^6.9.7",
|
"nodemailer": "^6.9.7",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
"passport-github": "^1.1.0",
|
"passport-github": "^1.1.0",
|
||||||
@@ -60,7 +62,6 @@
|
|||||||
"zod-to-json-schema": "^3.22.0"
|
"zod-to-json-schema": "^3.22.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/jmespath": "^0.15.2",
|
"@types/jmespath": "^0.15.2",
|
||||||
"@types/jsonwebtoken": "^9.0.5",
|
"@types/jsonwebtoken": "^9.0.5",
|
||||||
@@ -2313,8 +2314,7 @@
|
|||||||
"node_modules/@octokit/webhooks-types": {
|
"node_modules/@octokit/webhooks-types": {
|
||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.3.1.tgz",
|
||||||
"integrity": "sha512-u6355ZsZnHwmxen30SrqnYb1pXieBFkYgkNzt+Ed4Ao5tupN1OErHfzwiV6hq6duGkDAYASbq7/uVJQ69PjLEg==",
|
"integrity": "sha512-u6355ZsZnHwmxen30SrqnYb1pXieBFkYgkNzt+Ed4Ao5tupN1OErHfzwiV6hq6duGkDAYASbq7/uVJQ69PjLEg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/webhooks/node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/webhooks/node_modules/@octokit/openapi-types": {
|
||||||
"version": "12.11.0",
|
"version": "12.11.0",
|
||||||
@@ -5342,6 +5342,14 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/cluster-key-slot": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||||
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
|
"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": {
|
"node_modules/node-fetch": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
|||||||
@@ -78,8 +78,8 @@
|
|||||||
"@fastify/swagger-ui": "^1.10.1",
|
"@fastify/swagger-ui": "^1.10.1",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"argon2": "^0.31.2",
|
"argon2": "^0.31.2",
|
||||||
"aws-sdk": "^2.1532.0",
|
"aws-sdk": "^2.1532.0",
|
||||||
@@ -101,6 +101,7 @@
|
|||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"mysql2": "^3.6.5",
|
"mysql2": "^3.6.5",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
|
"node-cache": "^5.1.2",
|
||||||
"nodemailer": "^6.9.7",
|
"nodemailer": "^6.9.7",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
"passport-github": "^1.1.0",
|
"passport-github": "^1.1.0",
|
||||||
|
|||||||
Vendored
+4
@@ -3,6 +3,7 @@ import "fastify";
|
|||||||
import { TUsers } from "@app/db/schemas";
|
import { TUsers } from "@app/db/schemas";
|
||||||
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-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";
|
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 { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
|
||||||
import { TSecretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service";
|
import { TSecretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service";
|
||||||
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-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 { TAuthMode } from "@app/server/plugins/auth/inject-identity";
|
||||||
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||||
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
||||||
@@ -101,6 +103,8 @@ declare module "fastify" {
|
|||||||
saml: TSamlConfigServiceFactory;
|
saml: TSamlConfigServiceFactory;
|
||||||
auditLog: TAuditLogServiceFactory;
|
auditLog: TAuditLogServiceFactory;
|
||||||
secretScanning: TSecretScanningServiceFactory;
|
secretScanning: TSecretScanningServiceFactory;
|
||||||
|
license: TLicenseServiceFactory;
|
||||||
|
trustedIp: TTrustedIpServiceFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
|
|||||||
Vendored
+8
@@ -148,6 +148,9 @@ import {
|
|||||||
TSuperAdmin,
|
TSuperAdmin,
|
||||||
TSuperAdminInsert,
|
TSuperAdminInsert,
|
||||||
TSuperAdminUpdate,
|
TSuperAdminUpdate,
|
||||||
|
TTrustedIps,
|
||||||
|
TTrustedIpsInsert,
|
||||||
|
TTrustedIpsUpdate,
|
||||||
TUserActions,
|
TUserActions,
|
||||||
TUserActionsInsert,
|
TUserActionsInsert,
|
||||||
TUserActionsUpdate,
|
TUserActionsUpdate,
|
||||||
@@ -393,6 +396,11 @@ declare module "knex/types/tables" {
|
|||||||
TSecretScanningGitRisksInsert,
|
TSecretScanningGitRisksInsert,
|
||||||
TSecretScanningGitRisksUpdate
|
TSecretScanningGitRisksUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.TrustedIps]: Knex.CompositeTableType<
|
||||||
|
TTrustedIps,
|
||||||
|
TTrustedIpsInsert,
|
||||||
|
TTrustedIpsUpdate
|
||||||
|
>;
|
||||||
// Junction tables
|
// Junction tables
|
||||||
[TableName.JnSecretTag]: Knex.CompositeTableType<
|
[TableName.JnSecretTag]: Knex.CompositeTableType<
|
||||||
TSecretTagJunction,
|
TSecretTagJunction,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
|||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
if (!(await knex.schema.hasTable(TableName.Project))) {
|
if (!(await knex.schema.hasTable(TableName.Project))) {
|
||||||
await knex.schema.createTable(TableName.Project, (t) => {
|
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.string("name").notNullable();
|
||||||
t.boolean("autoCapitalization").defaultTo(true);
|
t.boolean("autoCapitalization").defaultTo(true);
|
||||||
t.uuid("orgId").notNullable();
|
t.uuid("orgId").notNullable();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
|||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
if (!(await knex.schema.hasTable(TableName.ServiceToken))) {
|
if (!(await knex.schema.hasTable(TableName.ServiceToken))) {
|
||||||
await knex.schema.createTable(TableName.ServiceToken, (t) => {
|
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.string("name").notNullable();
|
||||||
t.jsonb("scopes").notNullable();
|
t.jsonb("scopes").notNullable();
|
||||||
t.specificType("permissions", "text[]").notNullable();
|
t.specificType("permissions", "text[]").notNullable();
|
||||||
|
|||||||
@@ -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 "./secrets";
|
||||||
export * from "./service-tokens";
|
export * from "./service-tokens";
|
||||||
export * from "./super-admin";
|
export * from "./super-admin";
|
||||||
|
export * from "./trusted-ips";
|
||||||
export * from "./user-actions";
|
export * from "./user-actions";
|
||||||
export * from "./user-encryption-keys";
|
export * from "./user-encryption-keys";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export enum TableName {
|
|||||||
GitAppInstallSession = "git_app_install_sessions",
|
GitAppInstallSession = "git_app_install_sessions",
|
||||||
GitAppOrg = "git_app_org",
|
GitAppOrg = "git_app_org",
|
||||||
SecretScanningGitRisk = "secret_scanning_git_risks",
|
SecretScanningGitRisk = "secret_scanning_git_risks",
|
||||||
|
TrustedIps = "trusted_ips",
|
||||||
// junction tables
|
// junction tables
|
||||||
JnSecretTag = "secret_tag_junction",
|
JnSecretTag = "secret_tag_junction",
|
||||||
JnSecretVersionTag = "secret_version_tag_junction"
|
JnSecretVersionTag = "secret_version_tag_junction"
|
||||||
|
|||||||
@@ -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 { registerOrgRoleRouter } from "./org-role-router";
|
||||||
import { registerProjectRoleRouter } from "./project-role-router";
|
import { registerProjectRoleRouter } from "./project-role-router";
|
||||||
import { registerProjectRouter } from "./project-router";
|
import { registerProjectRouter } from "./project-router";
|
||||||
@@ -8,14 +9,17 @@ import { registerSecretRotationProviderRouter } from "./secret-rotation-provider
|
|||||||
import { registerSecretRotationRouter } from "./secret-rotation-router";
|
import { registerSecretRotationRouter } from "./secret-rotation-router";
|
||||||
import { registerSecretScanningRouter } from "./secret-scanning-router";
|
import { registerSecretScanningRouter } from "./secret-scanning-router";
|
||||||
import { registerSnapshotRouter } from "./snapshot-router";
|
import { registerSnapshotRouter } from "./snapshot-router";
|
||||||
|
import { registerTrustedIpRouter } from "./trusted-ip-router";
|
||||||
|
|
||||||
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||||
// org role starts with organization
|
// org role starts with organization
|
||||||
await server.register(registerOrgRoleRouter, { prefix: "/organization" });
|
await server.register(registerOrgRoleRouter, { prefix: "/organization" });
|
||||||
|
await server.register(registerLicenseRouter, { prefix: "/organizations" });
|
||||||
await server.register(
|
await server.register(
|
||||||
async (projectServer) => {
|
async (projectRouter) => {
|
||||||
projectServer.register(registerProjectRoleRouter);
|
await projectRouter.register(registerProjectRoleRouter);
|
||||||
projectServer.register(registerProjectRouter);
|
await projectRouter.register(registerProjectRouter);
|
||||||
|
await projectRouter.register(registerTrustedIpRouter);
|
||||||
},
|
},
|
||||||
{ prefix: "/workspace" }
|
{ prefix: "/workspace" }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -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 { 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 { TAuditLogDalFactory } from "./audit-log-dal";
|
||||||
import { TCreateAuditLogDTO } from "./audit-log-types";
|
import { TCreateAuditLogDTO } from "./audit-log-types";
|
||||||
|
|
||||||
type TAuditLogQueueServiceFactoryDep = {
|
type TAuditLogQueueServiceFactoryDep = {
|
||||||
auditLogDal: TAuditLogDalFactory;
|
auditLogDal: TAuditLogDalFactory;
|
||||||
queueService: TQueueServiceFactory;
|
queueService: TQueueServiceFactory;
|
||||||
|
projectDal: Pick<TProjectDalFactory, "findById">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAuditLogQueueServiceFactory = ReturnType<typeof auditLogQueueServiceFactory>;
|
export type TAuditLogQueueServiceFactory = ReturnType<typeof auditLogQueueServiceFactory>;
|
||||||
|
|
||||||
export const auditLogQueueServiceFactory = ({
|
export const auditLogQueueServiceFactory = ({
|
||||||
auditLogDal,
|
auditLogDal,
|
||||||
queueService
|
queueService,
|
||||||
|
projectDal,
|
||||||
|
licenseService
|
||||||
}: TAuditLogQueueServiceFactoryDep) => {
|
}: TAuditLogQueueServiceFactoryDep) => {
|
||||||
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
||||||
await queueService.queue(QueueName.AuditLog, QueueJobs.AuditLog, data, {
|
await queueService.queue(QueueName.AuditLog, QueueJobs.AuditLog, data, {
|
||||||
@@ -24,8 +30,19 @@ export const auditLogQueueServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
queueService.start(QueueName.AuditLog, async (job) => {
|
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;
|
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({
|
await auditLogDal.create({
|
||||||
actor: actor.type,
|
actor: actor.type,
|
||||||
actorMetadata: actor.metadata,
|
actorMetadata: actor.metadata,
|
||||||
@@ -34,7 +51,7 @@ export const auditLogQueueServiceFactory = ({
|
|||||||
ipAddress,
|
ipAddress,
|
||||||
orgId,
|
orgId,
|
||||||
eventType: event.type,
|
eventType: event.type,
|
||||||
expiresAt: new Date(Date.now() + 30 * MS_IN_DAY),
|
expiresAt: new Date(Date.now() + ttl),
|
||||||
eventMetadata: event.metadata,
|
eventMetadata: event.metadata,
|
||||||
userAgentType
|
userAgentType
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 { 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 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 { 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 { 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 = {
|
type TLicenseServiceFactoryDep = {
|
||||||
|
orgDal: Pick<TOrgDalFactory, "findOrgById">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
licenseDal: TLicenseDalFactory;
|
licenseDal: TLicenseDalFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
|
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 appCfg = getConfig();
|
||||||
const licenceApi = axios.create({
|
const licenseServerCloudApi = setupLicenceRequestWithStore(
|
||||||
baseURL: appCfg.LICENCE_SERVER_URL
|
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 generateOrgCustomerId = async (orgName: string, email: string) => {
|
||||||
const {
|
if (instanceType === InstanceType.Cloud) {
|
||||||
data: { customerId }
|
const {
|
||||||
} = await licenceApi.post("/api/license-server/v1/customers", { email, name: orgName });
|
data: { customerId }
|
||||||
return 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) => {
|
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 {
|
return {
|
||||||
generateOrgCustomerId,
|
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 { TOrgDalFactory } from "@app/services/org/org-dal";
|
||||||
import { TUserDalFactory } from "@app/services/user/user-dal";
|
import { TUserDalFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { TSamlConfigDalFactory } from "./saml-config-dal";
|
import { TSamlConfigDalFactory } from "./saml-config-dal";
|
||||||
@@ -40,6 +41,7 @@ type TSamlConfigServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
orgBotDal: Pick<TOrgBotDalFactory, "findOne">;
|
orgBotDal: Pick<TOrgBotDalFactory, "findOne">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSamlConfigServiceFactory = ReturnType<typeof samlConfigServiceFactory>;
|
export type TSamlConfigServiceFactory = ReturnType<typeof samlConfigServiceFactory>;
|
||||||
@@ -49,7 +51,8 @@ export const samlConfigServiceFactory = ({
|
|||||||
orgBotDal,
|
orgBotDal,
|
||||||
orgDal,
|
orgDal,
|
||||||
userDal,
|
userDal,
|
||||||
permissionService
|
permissionService,
|
||||||
|
licenseService
|
||||||
}: TSamlConfigServiceFactoryDep) => {
|
}: TSamlConfigServiceFactoryDep) => {
|
||||||
const createSamlCfg = async ({
|
const createSamlCfg = async ({
|
||||||
cert,
|
cert,
|
||||||
@@ -67,7 +70,12 @@ export const samlConfigServiceFactory = ({
|
|||||||
OrgPermissionSubjects.Sso
|
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 });
|
const orgBot = await orgBotDal.findOne({ orgId });
|
||||||
if (!orgBot)
|
if (!orgBot)
|
||||||
@@ -123,6 +131,13 @@ export const samlConfigServiceFactory = ({
|
|||||||
OrgPermissionActions.Edit,
|
OrgPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.Sso
|
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 updateQuery: TSamlConfigsUpdate = { authProvider, isActive };
|
||||||
const orgBot = await orgBotDal.findOne({ orgId });
|
const orgBot = await orgBotDal.findOne({ orgId });
|
||||||
if (!orgBot)
|
if (!orgBot)
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import Ajv from "ajv";
|
|||||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
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 { TProjectEnvDalFactory } from "@app/services/project-env/project-env-dal";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TSecretRotationDalFactory } from "./secret-rotation-dal";
|
import { TSecretRotationDalFactory } from "./secret-rotation-dal";
|
||||||
@@ -22,6 +24,8 @@ import { rotationTemplates } from "./templates";
|
|||||||
|
|
||||||
type TSecretRotationServiceFactoryDep = {
|
type TSecretRotationServiceFactoryDep = {
|
||||||
secretRotationDal: TSecretRotationDalFactory;
|
secretRotationDal: TSecretRotationDalFactory;
|
||||||
|
projectDal: Pick<TProjectDalFactory, "findById">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
projectEnvDal: Pick<TProjectEnvDalFactory, "findOne">;
|
projectEnvDal: Pick<TProjectEnvDalFactory, "findOne">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
secretRotationQueue: TSecretRotationQueueFactory;
|
secretRotationQueue: TSecretRotationQueueFactory;
|
||||||
@@ -34,7 +38,9 @@ export const secretRotationServiceFactory = ({
|
|||||||
secretRotationDal,
|
secretRotationDal,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDal,
|
projectEnvDal,
|
||||||
secretRotationQueue
|
secretRotationQueue,
|
||||||
|
licenseService,
|
||||||
|
projectDal
|
||||||
}: TSecretRotationServiceFactoryDep) => {
|
}: TSecretRotationServiceFactoryDep) => {
|
||||||
const getProviderTemplates = async ({ actor, actorId, projectId }: TProjectPermission) => {
|
const getProviderTemplates = async ({ actor, actorId, projectId }: TProjectPermission) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||||
@@ -68,6 +74,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
const env = await projectEnvDal.findOne({ slug: environment });
|
const env = await projectEnvDal.findOne({ slug: environment });
|
||||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
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);
|
const selectedTemplate = rotationTemplates.find(({ name }) => name === provider);
|
||||||
if (!selectedTemplate) throw new BadRequestError({ message: "Provider not found" });
|
if (!selectedTemplate) throw new BadRequestError({ message: "Provider not found" });
|
||||||
const formattedInputs: Record<string, unknown> = {};
|
const formattedInputs: Record<string, unknown> = {};
|
||||||
@@ -149,6 +163,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
const doc = await secretRotationDal.findById(rotationId);
|
const doc = await secretRotationDal.findById(rotationId);
|
||||||
if (!doc) throw new BadRequestError({ message: "Rotation not found" });
|
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(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
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 { groupBy } from "@app/lib/fn";
|
||||||
import { TSecretDalFactory } from "@app/services/secret/secret-dal";
|
import { TSecretDalFactory } from "@app/services/secret/secret-dal";
|
||||||
import { TSecretVersionDalFactory } from "@app/services/secret/secret-version-dal";
|
import { TSecretVersionDalFactory } from "@app/services/secret/secret-version-dal";
|
||||||
import { TSecretFolderDalFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { TSecretFolderDalFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
import { TSecretFolderVersionDalFactory } from "@app/services/secret-folder/secret-folder-version-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 { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import {
|
import {
|
||||||
@@ -34,6 +35,7 @@ type TSecretSnapshotServiceFactoryDep = {
|
|||||||
"findById" | "findBySecretPath" | "delete" | "insertMany"
|
"findById" | "findBySecretPath" | "delete" | "insertMany"
|
||||||
>;
|
>;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "isValidLicense">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretSnapshotServiceFactory = ReturnType<typeof secretSnapshotServiceFactory>;
|
export type TSecretSnapshotServiceFactory = ReturnType<typeof secretSnapshotServiceFactory>;
|
||||||
@@ -46,7 +48,8 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
snapshotFolderDal,
|
snapshotFolderDal,
|
||||||
folderDal,
|
folderDal,
|
||||||
secretDal,
|
secretDal,
|
||||||
permissionService
|
permissionService,
|
||||||
|
licenseService
|
||||||
}: TSecretSnapshotServiceFactoryDep) => {
|
}: TSecretSnapshotServiceFactoryDep) => {
|
||||||
const projectSecretSnapshotCount = async ({
|
const projectSecretSnapshotCount = async ({
|
||||||
environment,
|
environment,
|
||||||
@@ -109,6 +112,9 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const performSnapshot = async (folderId: string) => {
|
const performSnapshot = async (folderId: string) => {
|
||||||
|
if (!licenseService.isValidLicense)
|
||||||
|
throw new InternalServerError({ message: "Invalid license" });
|
||||||
|
|
||||||
const snapshot = await snapshotDal.transaction(async (tx) => {
|
const snapshot = await snapshotDal.transaction(async (tx) => {
|
||||||
const folder = await folderDal.findById(folderId, tx);
|
const folder = await folderDal.findById(folderId, tx);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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_GIT_APP_ID: zpStr(z.string().optional()),
|
||||||
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
|
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
|
||||||
// LICENCE
|
// LICENCE
|
||||||
LICENCE_SERVER_URL: zpStr(z.string().optional()),
|
LICENSE_SERVER_URL: zpStr(z.string().optional()),
|
||||||
LICENCE_SERVER_KEY: zpStr(z.string().optional())
|
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
|
||||||
|
LICENSE_KEY: zpStr(z.string().optional())
|
||||||
})
|
})
|
||||||
.transform((data) => ({
|
.transform((data) => ({
|
||||||
...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 {
|
export class UnauthorizedError extends Error {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import fp from "fastify-plugin";
|
|||||||
|
|
||||||
import { jsonSchemaTransform } from "./fastify-zod";
|
import { jsonSchemaTransform } from "./fastify-zod";
|
||||||
|
|
||||||
// TODO(akhilmhdh-pg): change the localhost port later
|
|
||||||
export const fastifySwagger = fp(async (fastify) => {
|
export const fastifySwagger = fp(async (fastify) => {
|
||||||
await fastify.register(swagger, {
|
await fastify.register(swagger, {
|
||||||
transform: jsonSchemaTransform,
|
transform: jsonSchemaTransform,
|
||||||
@@ -16,7 +15,7 @@ export const fastifySwagger = fp(async (fastify) => {
|
|||||||
},
|
},
|
||||||
servers: [
|
servers: [
|
||||||
{
|
{
|
||||||
url: "http://localhost:4000",
|
url: "http://localhost:8080",
|
||||||
description: "Local server"
|
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 { auditLogDalFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
||||||
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
||||||
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
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 { permissionDalFactory } from "@app/ee/services/permission/permission-dal";
|
||||||
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { samlConfigDalFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
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 { snapshotDalFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
||||||
import { snapshotFolderDalFactory } from "@app/ee/services/secret-snapshot/snapshot-folder-dal";
|
import { snapshotFolderDalFactory } from "@app/ee/services/secret-snapshot/snapshot-folder-dal";
|
||||||
import { snapshotSecretDalFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-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 { getConfig } from "@app/lib/config/env";
|
||||||
import { TQueueServiceFactory } from "@app/queue";
|
import { TQueueServiceFactory } from "@app/queue";
|
||||||
import { apiKeyDalFactory } from "@app/services/api-key/api-key-dal";
|
import { apiKeyDalFactory } from "@app/services/api-key/api-key-dal";
|
||||||
@@ -150,6 +154,7 @@ export const registerRoutes = async (
|
|||||||
const identityUaClientSecretDal = identityUaClientSecretDalFactory(db);
|
const identityUaClientSecretDal = identityUaClientSecretDalFactory(db);
|
||||||
|
|
||||||
const auditLogDal = auditLogDalFactory(db);
|
const auditLogDal = auditLogDalFactory(db);
|
||||||
|
const trustedIpDal = trustedIpDalFactory(db);
|
||||||
|
|
||||||
// ee db layer ops
|
// ee db layer ops
|
||||||
const permissionDal = permissionDalFactory(db);
|
const permissionDal = permissionDalFactory(db);
|
||||||
@@ -168,6 +173,7 @@ export const registerRoutes = async (
|
|||||||
const gitAppInstallSessionDal = gitAppInstallSessionDalFactory(db);
|
const gitAppInstallSessionDal = gitAppInstallSessionDalFactory(db);
|
||||||
const gitAppOrgDal = gitAppDalFactory(db);
|
const gitAppOrgDal = gitAppDalFactory(db);
|
||||||
const secretScanningDal = secretScanningDalFactory(db);
|
const secretScanningDal = secretScanningDalFactory(db);
|
||||||
|
const licenseDal = licenseDalFactory(db);
|
||||||
|
|
||||||
const permissionService = permissionServiceFactory({
|
const permissionService = permissionServiceFactory({
|
||||||
permissionDal,
|
permissionDal,
|
||||||
@@ -175,7 +181,19 @@ export const registerRoutes = async (
|
|||||||
projectRoleDal,
|
projectRoleDal,
|
||||||
serviceTokenDal
|
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 auditLogService = auditLogServiceFactory({ auditLogDal, permissionService, auditLogQueue });
|
||||||
const sapService = secretApprovalPolicyServiceFactory({
|
const sapService = secretApprovalPolicyServiceFactory({
|
||||||
projectMembershipDal,
|
projectMembershipDal,
|
||||||
@@ -189,7 +207,8 @@ export const registerRoutes = async (
|
|||||||
orgBotDal,
|
orgBotDal,
|
||||||
orgDal,
|
orgDal,
|
||||||
userDal,
|
userDal,
|
||||||
samlConfigDal
|
samlConfigDal,
|
||||||
|
licenseService
|
||||||
});
|
});
|
||||||
|
|
||||||
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal, userDal });
|
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal, userDal });
|
||||||
@@ -202,6 +221,8 @@ export const registerRoutes = async (
|
|||||||
userDal
|
userDal
|
||||||
});
|
});
|
||||||
const orgService = orgServiceFactory({
|
const orgService = orgServiceFactory({
|
||||||
|
licenseService,
|
||||||
|
samlConfigDal,
|
||||||
orgRoleDal,
|
orgRoleDal,
|
||||||
permissionService,
|
permissionService,
|
||||||
orgDal,
|
orgDal,
|
||||||
@@ -217,7 +238,8 @@ export const registerRoutes = async (
|
|||||||
authDal,
|
authDal,
|
||||||
userDal,
|
userDal,
|
||||||
orgDal,
|
orgDal,
|
||||||
orgService
|
orgService,
|
||||||
|
licenseService
|
||||||
});
|
});
|
||||||
const orgRoleService = orgRoleServiceFactory({ permissionService, orgRoleDal });
|
const orgRoleService = orgRoleServiceFactory({ permissionService, orgRoleDal });
|
||||||
const superAdminService = superAdminServiceFactory({
|
const superAdminService = superAdminServiceFactory({
|
||||||
@@ -247,7 +269,8 @@ export const registerRoutes = async (
|
|||||||
secretBlindIndexDal,
|
secretBlindIndexDal,
|
||||||
projectEnvDal,
|
projectEnvDal,
|
||||||
projectMembershipDal,
|
projectMembershipDal,
|
||||||
folderDal
|
folderDal,
|
||||||
|
licenseService
|
||||||
});
|
});
|
||||||
const projectMembershipService = projectMembershipServiceFactory({
|
const projectMembershipService = projectMembershipServiceFactory({
|
||||||
projectMembershipDal,
|
projectMembershipDal,
|
||||||
@@ -257,9 +280,15 @@ export const registerRoutes = async (
|
|||||||
userDal,
|
userDal,
|
||||||
smtpService,
|
smtpService,
|
||||||
projectKeyDal,
|
projectKeyDal,
|
||||||
projectRoleDal
|
projectRoleDal,
|
||||||
|
licenseService
|
||||||
|
});
|
||||||
|
const projectEnvService = projectEnvServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
projectEnvDal,
|
||||||
|
licenseService,
|
||||||
|
projectDal
|
||||||
});
|
});
|
||||||
const projectEnvService = projectEnvServiceFactory({ permissionService, projectEnvDal });
|
|
||||||
const projectKeyService = projectKeyServiceFactory({
|
const projectKeyService = projectKeyServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
projectKeyDal,
|
projectKeyDal,
|
||||||
@@ -275,7 +304,8 @@ export const registerRoutes = async (
|
|||||||
snapshotSecretDal,
|
snapshotSecretDal,
|
||||||
secretVersionDal,
|
secretVersionDal,
|
||||||
folderVersionDal,
|
folderVersionDal,
|
||||||
permissionService
|
permissionService,
|
||||||
|
licenseService
|
||||||
});
|
});
|
||||||
const webhookService = webhookServiceFactory({
|
const webhookService = webhookServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -350,7 +380,9 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDal,
|
projectEnvDal,
|
||||||
secretRotationDal,
|
secretRotationDal,
|
||||||
secretRotationQueue
|
secretRotationQueue,
|
||||||
|
projectDal,
|
||||||
|
licenseService
|
||||||
});
|
});
|
||||||
|
|
||||||
const integrationService = integrationServiceFactory({
|
const integrationService = integrationServiceFactory({
|
||||||
@@ -383,10 +415,13 @@ export const registerRoutes = async (
|
|||||||
identityDal,
|
identityDal,
|
||||||
identityAccessTokenDal,
|
identityAccessTokenDal,
|
||||||
identityUaClientSecretDal,
|
identityUaClientSecretDal,
|
||||||
identityUaDal
|
identityUaDal,
|
||||||
|
licenseService
|
||||||
});
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
|
// setup the communication with license key server
|
||||||
|
await licenseService.init();
|
||||||
// inject all services
|
// inject all services
|
||||||
server.decorate<FastifyZodProvider["services"]>("services", {
|
server.decorate<FastifyZodProvider["services"]>("services", {
|
||||||
login: loginService,
|
login: loginService,
|
||||||
@@ -423,7 +458,9 @@ export const registerRoutes = async (
|
|||||||
snapshot: snapshotService,
|
snapshot: snapshotService,
|
||||||
saml: samlService,
|
saml: samlService,
|
||||||
auditLog: auditLogService,
|
auditLog: auditLogService,
|
||||||
secretScanning: secretScanningService
|
secretScanning: secretScanningService,
|
||||||
|
license: licenseService,
|
||||||
|
trustedIp: trustedIpService
|
||||||
});
|
});
|
||||||
|
|
||||||
server.decorate<FastifyZodProvider["store"]>("store", {
|
server.decorate<FastifyZodProvider["store"]>("store", {
|
||||||
|
|||||||
@@ -39,7 +39,11 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { identityUa, accessToken, identityAccessToken, validClientSecretInfo } =
|
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({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ import { registerWebhookRouter } from "./webhook-router";
|
|||||||
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||||
await server.register(registerSsoRouter, { prefix: "/sso" });
|
await server.register(registerSsoRouter, { prefix: "/sso" });
|
||||||
await server.register(
|
await server.register(
|
||||||
async (authServer) => {
|
async (authRouter) => {
|
||||||
await authServer.register(registerAuthRoutes);
|
await authRouter.register(registerAuthRoutes);
|
||||||
await authServer.register(registerIdentityUaRouter);
|
await authRouter.register(registerIdentityUaRouter);
|
||||||
await authServer.register(registerIdentityAccessTokenRouter);
|
await authRouter.register(registerIdentityAccessTokenRouter);
|
||||||
},
|
},
|
||||||
{ prefix: "/auth" }
|
{ prefix: "/auth" }
|
||||||
);
|
);
|
||||||
@@ -41,12 +41,12 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerSecretFolderRouter, { prefix: "/folders" });
|
await server.register(registerSecretFolderRouter, { prefix: "/folders" });
|
||||||
|
|
||||||
await server.register(
|
await server.register(
|
||||||
async (projectServer) => {
|
async (projectRouter) => {
|
||||||
await projectServer.register(registerProjectRouter);
|
await projectRouter.register(registerProjectRouter);
|
||||||
await projectServer.register(registerProjectEnvRouter);
|
await projectRouter.register(registerProjectEnvRouter);
|
||||||
await projectServer.register(registerProjectKeyRouter);
|
await projectRouter.register(registerProjectKeyRouter);
|
||||||
await projectServer.register(registerProjectMembershipRouter);
|
await projectRouter.register(registerProjectMembershipRouter);
|
||||||
await projectServer.register(registerSecretTagRouter);
|
await projectRouter.register(registerSecretTagRouter);
|
||||||
},
|
},
|
||||||
{ prefix: "/workspace" }
|
{ prefix: "/workspace" }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -84,8 +84,6 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(akhilmhdh-pg): missing my-workspace list
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:organizationId/name",
|
url: "/:organizationId/name",
|
||||||
@@ -161,7 +159,6 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:organizationId/incidentContactOrg/:incidentContactId",
|
url: "/:organizationId/incidentContactOrg/:incidentContactId",
|
||||||
schema: {
|
schema: {
|
||||||
// TODO(akhilmhdh-pg): change accept id instead of email
|
|
||||||
params: z.object({ organizationId: z.string().trim(), incidentContactId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim(), incidentContactId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
if (req.auth.actor !== ActorType.USER) return;
|
if (req.auth.actor !== ActorType.USER) return;
|
||||||
|
|
||||||
const membership = await server.services.org.deleteOrgMembership({
|
const membership = await server.services.org.deleteOrgMembership({
|
||||||
userId: req.permission.id,
|
userId: req.permission.id,
|
||||||
orgId: req.params.organizationId,
|
orgId: req.params.organizationId,
|
||||||
@@ -117,6 +117,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
const organization = await server.services.org.createOrganization(
|
const organization = await server.services.org.createOrganization(
|
||||||
req.permission.id,
|
req.permission.id,
|
||||||
|
req.auth.user.email,
|
||||||
req.body.name
|
req.body.name
|
||||||
);
|
);
|
||||||
return { organization };
|
return { organization };
|
||||||
|
|||||||
@@ -11,27 +11,45 @@ export type TTokenDalConfig = {};
|
|||||||
|
|
||||||
export type TTokenDalFactory = ReturnType<typeof tokenDalFactory>;
|
export type TTokenDalFactory = ReturnType<typeof tokenDalFactory>;
|
||||||
|
|
||||||
// TODO(akhilmhdh-pg): wrap all with database error
|
|
||||||
export const tokenDalFactory = (db: TDbClient) => {
|
export const tokenDalFactory = (db: TDbClient) => {
|
||||||
const authOrm = ormify(db, TableName.AuthTokens);
|
const authOrm = ormify(db, TableName.AuthTokens);
|
||||||
|
|
||||||
const findOneTokenSession = async (
|
const findOneTokenSession = async (
|
||||||
filter: Partial<TAuthTokenSessions>
|
filter: Partial<TAuthTokenSessions>
|
||||||
): Promise<TAuthTokenSessions | undefined> =>
|
): Promise<TAuthTokenSessions | undefined> => {
|
||||||
db(TableName.AuthTokenSession).where(filter).first();
|
try {
|
||||||
|
const doc = await db(TableName.AuthTokenSession).where(filter).first();
|
||||||
|
return doc;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "FindOneTokenSession" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const deleteTokenForUser = async ({
|
const deleteTokenForUser = async ({
|
||||||
userId,
|
userId,
|
||||||
type,
|
type,
|
||||||
orgId
|
orgId
|
||||||
}: TDeleteTokenForUserDalDTO): Promise<TAuthTokens[] | undefined> =>
|
}: TDeleteTokenForUserDalDTO): Promise<TAuthTokens[] | undefined> => {
|
||||||
db(TableName.AuthTokens).where({ userId, type, orgId }).delete().returning("*");
|
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 ({
|
const decrementTriesField = async ({
|
||||||
userId,
|
userId,
|
||||||
type
|
type
|
||||||
}: TDeleteTokenForUserDalDTO): Promise<void> => {
|
}: 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) => {
|
const findTokenSessions = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
|
||||||
@@ -48,29 +66,37 @@ export const tokenDalFactory = (db: TDbClient) => {
|
|||||||
ip: string,
|
ip: string,
|
||||||
userAgent: string
|
userAgent: string
|
||||||
): Promise<TAuthTokenSessions | undefined> => {
|
): Promise<TAuthTokenSessions | undefined> => {
|
||||||
const [session] = await db(TableName.AuthTokenSession)
|
try {
|
||||||
.insert({
|
const [session] = await db(TableName.AuthTokenSession)
|
||||||
userId,
|
.insert({
|
||||||
ip,
|
userId,
|
||||||
userAgent,
|
ip,
|
||||||
accessVersion: 1,
|
userAgent,
|
||||||
refreshVersion: 1,
|
accessVersion: 1,
|
||||||
lastUsed: new Date()
|
refreshVersion: 1,
|
||||||
})
|
lastUsed: new Date()
|
||||||
.returning("*");
|
})
|
||||||
return session;
|
.returning("*");
|
||||||
|
return session;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "InsertTokenSession" });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const incrementTokenSessionVersion = async (
|
const incrementTokenSessionVersion = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
sessionId: string
|
sessionId: string
|
||||||
): Promise<TAuthTokenSessions | undefined> => {
|
): Promise<TAuthTokenSessions | undefined> => {
|
||||||
const [session] = await db(TableName.AuthTokenSession)
|
try {
|
||||||
.where({ userId, id: sessionId })
|
const [session] = await db(TableName.AuthTokenSession)
|
||||||
.increment("accessVersion", 1)
|
.where({ userId, id: sessionId })
|
||||||
.increment("refreshVersion", 1)
|
.increment("accessVersion", 1)
|
||||||
.returning("*");
|
.increment("refreshVersion", 1)
|
||||||
return session;
|
.returning("*");
|
||||||
|
return session;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "IncrementTokenSessionVersion" });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteTokenSession = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
|
const deleteTokenSession = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import { OrgMembershipStatus } from "@app/db/schemas";
|
import { OrgMembershipStatus } from "@app/db/schemas";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { isDisposableEmail } from "@app/lib/validator";
|
import { isDisposableEmail } from "@app/lib/validator";
|
||||||
@@ -22,6 +23,7 @@ type TAuthSignupDep = {
|
|||||||
orgDal: TOrgDalFactory;
|
orgDal: TOrgDalFactory;
|
||||||
tokenService: TAuthTokenServiceFactory;
|
tokenService: TAuthTokenServiceFactory;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAuthSignupFactory = ReturnType<typeof authSignupServiceFactory>;
|
export type TAuthSignupFactory = ReturnType<typeof authSignupServiceFactory>;
|
||||||
@@ -31,7 +33,8 @@ export const authSignupServiceFactory = ({
|
|||||||
tokenService,
|
tokenService,
|
||||||
smtpService,
|
smtpService,
|
||||||
orgService,
|
orgService,
|
||||||
orgDal
|
orgDal,
|
||||||
|
licenseService
|
||||||
}: TAuthSignupDep) => {
|
}: TAuthSignupDep) => {
|
||||||
// first step of signup. create user and send email
|
// first step of signup. create user and send email
|
||||||
const beginEmailSignupProcess = async (email: string) => {
|
const beginEmailSignupProcess = async (email: string) => {
|
||||||
@@ -143,13 +146,17 @@ export const authSignupServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!hasSamlEnabled) {
|
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 },
|
{ inviteEmail: email, status: OrgMembershipStatus.Invited },
|
||||||
{ userId: user.id, status: OrgMembershipStatus.Accepted }
|
{ 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({
|
const tokenSession = await tokenService.getUserTokenSession({
|
||||||
userAgent,
|
userAgent,
|
||||||
@@ -238,11 +245,16 @@ export const authSignupServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
await orgDal.updateMembership(
|
const updatedMembersips = await orgDal.updateMembership(
|
||||||
{ inviteEmail: email, status: OrgMembershipStatus.Invited },
|
{ inviteEmail: email, status: OrgMembershipStatus.Invited },
|
||||||
{ userId: us.id, status: OrgMembershipStatus.Accepted },
|
{ userId: us.id, status: OrgMembershipStatus.Accepted },
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
const uniqueOrgId = [...new Set(updatedMembersips.map(({ orgId }) => orgId))];
|
||||||
|
await Promise.allSettled(
|
||||||
|
uniqueOrgId.map((orgId) => licenseService.updateSubscriptionOrgMemberCount(orgId))
|
||||||
|
);
|
||||||
|
|
||||||
return { info: us, key: userEncKey };
|
return { info: us, key: userEncKey };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import bcrypt from "bcrypt";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import { IdentityAuthMethod } from "@app/db/schemas";
|
import { IdentityAuthMethod } from "@app/db/schemas";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects
|
OrgPermissionSubjects
|
||||||
@@ -13,7 +14,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
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 { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||||
import { TIdentityDalFactory } from "../identity/identity-dal";
|
import { TIdentityDalFactory } from "../identity/identity-dal";
|
||||||
@@ -38,6 +39,7 @@ type TIdentityUaServiceFactoryDep = {
|
|||||||
identityOrgMembershipDal: TIdentityOrgDalFactory;
|
identityOrgMembershipDal: TIdentityOrgDalFactory;
|
||||||
identityDal: Pick<TIdentityDalFactory, "updateById">;
|
identityDal: Pick<TIdentityDalFactory, "updateById">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIdentityUaServiceFactory = ReturnType<typeof identityUaServiceFactory>;
|
export type TIdentityUaServiceFactory = ReturnType<typeof identityUaServiceFactory>;
|
||||||
@@ -48,13 +50,17 @@ export const identityUaServiceFactory = ({
|
|||||||
identityAccessTokenDal,
|
identityAccessTokenDal,
|
||||||
identityOrgMembershipDal,
|
identityOrgMembershipDal,
|
||||||
identityDal,
|
identityDal,
|
||||||
permissionService
|
permissionService,
|
||||||
|
licenseService
|
||||||
}: TIdentityUaServiceFactoryDep) => {
|
}: TIdentityUaServiceFactoryDep) => {
|
||||||
const login = async (clientId: string, clientSecret: string) => {
|
const login = async (clientId: string, clientSecret: string, ip: string) => {
|
||||||
const identityUa = await identityUaDal.findOne({ clientId });
|
const identityUa = await identityUaDal.findOne({ clientId });
|
||||||
if (!identityUa) throw new UnauthorizedError();
|
if (!identityUa) throw new UnauthorizedError();
|
||||||
|
|
||||||
// TODO(akhilmhdh-pg): add ip checking
|
checkIPAgainstBlocklist({
|
||||||
|
ipAddress: ip,
|
||||||
|
trustedIps: identityUa.clientSecretTrustedIps as TIp[]
|
||||||
|
});
|
||||||
const clientSecrtInfo = await identityUaClientSecretDal.find({
|
const clientSecrtInfo = await identityUaClientSecretDal.find({
|
||||||
identityUAId: identityUa.id,
|
identityUAId: identityUa.id,
|
||||||
isClientSecretRevoked: false
|
isClientSecretRevoked: false
|
||||||
@@ -166,10 +172,11 @@ export const identityUaServiceFactory = ({
|
|||||||
OrgPermissionActions.Create,
|
OrgPermissionActions.Create,
|
||||||
OrgPermissionSubjects.Identity
|
OrgPermissionSubjects.Identity
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
const reformattedClientSecretTrustedIps = clientSecretTrustedIps.map(
|
const reformattedClientSecretTrustedIps = clientSecretTrustedIps.map(
|
||||||
(clientSecretTrustedIp) => {
|
(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({
|
throw new BadRequestError({
|
||||||
message:
|
message:
|
||||||
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
|
"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) => {
|
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({
|
throw new BadRequestError({
|
||||||
message:
|
message:
|
||||||
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
|
"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,
|
OrgPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.Identity
|
OrgPermissionSubjects.Identity
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
const reformattedClientSecretTrustedIps = clientSecretTrustedIps?.map(
|
const reformattedClientSecretTrustedIps = clientSecretTrustedIps?.map(
|
||||||
(clientSecretTrustedIp) => {
|
(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({
|
throw new BadRequestError({
|
||||||
message:
|
message:
|
||||||
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
|
"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) => {
|
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({
|
throw new BadRequestError({
|
||||||
message:
|
message:
|
||||||
"Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
|
"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;
|
return newIdentity;
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(akhilmhdh-pg): add audit log here
|
|
||||||
return identity;
|
return identity;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -994,7 +994,6 @@ export const integrationAuthServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return delIntegrationAuth;
|
return delIntegrationAuth;
|
||||||
// TODO(akhilmhdh-pg): add audit log
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export const integrationServiceFactory = ({
|
|||||||
integration: integrationAuth.integration
|
integration: integrationAuth.integration
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(akhilmhdh-pg): audit log
|
|
||||||
return { integration, integrationAuth };
|
return { integration, integrationAuth };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
|
|||||||
import {
|
import {
|
||||||
TableName,
|
TableName,
|
||||||
TOrganizations,
|
TOrganizations,
|
||||||
|
TOrganizationsInsert,
|
||||||
TOrgMemberships,
|
TOrgMemberships,
|
||||||
TOrgMembershipsInsert,
|
TOrgMembershipsInsert,
|
||||||
TOrgMembershipsUpdate
|
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 {
|
try {
|
||||||
const [organization] = await (tx || db)(TableName.Organization)
|
const [organization] = await (tx || db)(TableName.Organization).insert(dto).returning("*");
|
||||||
.insert({ name })
|
|
||||||
.returning("*");
|
|
||||||
return organization;
|
return organization;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Create organization" });
|
throw new DatabaseError({ error, name: "Create organization" });
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects
|
OrgPermissionSubjects
|
||||||
} from "@app/ee/services/permission/org-permission";
|
} from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
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 { getConfig } from "@app/lib/config/env";
|
||||||
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
|
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
|
||||||
import { generateSymmetricKey, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { generateSymmetricKey, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
@@ -35,9 +37,14 @@ type TOrgServiceFactoryDep = {
|
|||||||
orgRoleDal: TOrgRoleDalFactory;
|
orgRoleDal: TOrgRoleDalFactory;
|
||||||
userDal: TUserDalFactory;
|
userDal: TUserDalFactory;
|
||||||
incidentContactDal: TIncidentContactsDalFactory;
|
incidentContactDal: TIncidentContactsDalFactory;
|
||||||
|
samlConfigDal: Pick<TSamlConfigDalFactory, "findOne">;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
tokenService: TAuthTokenServiceFactory;
|
tokenService: TAuthTokenServiceFactory;
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
|
licenseService: Pick<
|
||||||
|
TLicenseServiceFactory,
|
||||||
|
"getPlan" | "updateSubscriptionOrgMemberCount" | "generateOrgCustomerId" | "removeOrgCustomer"
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
||||||
@@ -50,7 +57,9 @@ export const orgServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
smtpService,
|
smtpService,
|
||||||
tokenService,
|
tokenService,
|
||||||
orgBotDal
|
orgBotDal,
|
||||||
|
licenseService,
|
||||||
|
samlConfigDal
|
||||||
}: TOrgServiceFactoryDep) => {
|
}: TOrgServiceFactoryDep) => {
|
||||||
/*
|
/*
|
||||||
* Get organization details by the organization id
|
* Get organization details by the organization id
|
||||||
@@ -99,7 +108,7 @@ export const orgServiceFactory = ({
|
|||||||
/*
|
/*
|
||||||
* Create organization
|
* Create organization
|
||||||
* */
|
* */
|
||||||
const createOrganization = async (userId: string, orgName: string) => {
|
const createOrganization = async (userId: string, userEmail: string, orgName: string) => {
|
||||||
const { privateKey, publicKey } = generateAsymmetricKeyPair();
|
const { privateKey, publicKey } = generateAsymmetricKeyPair();
|
||||||
const key = generateSymmetricKey();
|
const key = generateSymmetricKey();
|
||||||
const {
|
const {
|
||||||
@@ -117,8 +126,9 @@ export const orgServiceFactory = ({
|
|||||||
algorithm: symmetricKeyAlgorithm
|
algorithm: symmetricKeyAlgorithm
|
||||||
} = infisicalSymmetricEncypt(key);
|
} = infisicalSymmetricEncypt(key);
|
||||||
|
|
||||||
|
const customerId = await licenseService.generateOrgCustomerId(orgName, userEmail);
|
||||||
const organization = await orgDal.transaction(async (tx) => {
|
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(
|
await orgDal.createMembership(
|
||||||
{
|
{
|
||||||
userId,
|
userId,
|
||||||
@@ -161,6 +171,9 @@ export const orgServiceFactory = ({
|
|||||||
throw new UnauthorizedError({ name: "Delete org by id", message: "Not an admin" });
|
throw new UnauthorizedError({ name: "Delete org by id", message: "Not an admin" });
|
||||||
|
|
||||||
const organization = await orgDal.deleteById(orgId);
|
const organization = await orgDal.deleteById(orgId);
|
||||||
|
if (organization.customerId) {
|
||||||
|
await licenseService.removeOrgCustomer(organization.customerId);
|
||||||
|
}
|
||||||
return organization;
|
return organization;
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
@@ -184,6 +197,14 @@ export const orgServiceFactory = ({
|
|||||||
const customRole = await orgRoleDal.findOne({ slug: role, orgId });
|
const customRole = await orgRoleDal.findOne({ slug: role, orgId });
|
||||||
if (!customRole)
|
if (!customRole)
|
||||||
throw new BadRequestError({ name: "Update membership", message: "Role not found" });
|
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(
|
const [membership] = await orgDal.updateMembership(
|
||||||
{ id: membershipId, orgId },
|
{ id: membershipId, orgId },
|
||||||
{
|
{
|
||||||
@@ -210,7 +231,21 @@ export const orgServiceFactory = ({
|
|||||||
OrgPermissionSubjects.Member
|
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 invitee = await orgDal.transaction(async (tx) => {
|
||||||
const inviteeUser = await userDal.findUserByEmail(inviteeEmail, tx);
|
const inviteeUser = await userDal.findUserByEmail(inviteeEmail, tx);
|
||||||
if (inviteeUser) {
|
if (inviteeUser) {
|
||||||
@@ -284,6 +319,7 @@ export const orgServiceFactory = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||||
if (!appCfg.isSmtpConfigured) {
|
if (!appCfg.isSmtpConfigured) {
|
||||||
return `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${inviteeEmail}&organization_id=${org?.id}`;
|
return `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${inviteeEmail}&organization_id=${org?.id}`;
|
||||||
}
|
}
|
||||||
@@ -323,7 +359,7 @@ export const orgServiceFactory = ({
|
|||||||
orgId,
|
orgId,
|
||||||
status: OrgMembershipStatus.Accepted
|
status: OrgMembershipStatus.Accepted
|
||||||
});
|
});
|
||||||
// TODO(akhilmhdh-pg): update org licence subscription
|
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||||
return { user };
|
return { user };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,6 +386,8 @@ export const orgServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const membership = await orgDal.deleteMembershipById(membershipId, orgId);
|
const membership = await orgDal.deleteMembershipById(membershipId, orgId);
|
||||||
|
|
||||||
|
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||||
return membership;
|
return membership;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -7,19 +8,24 @@ import {
|
|||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { TProjectDalFactory } from "../project/project-dal";
|
||||||
import { TProjectEnvDalFactory } from "./project-env-dal";
|
import { TProjectEnvDalFactory } from "./project-env-dal";
|
||||||
import { TCreateEnvDTO, TDeleteEnvDTO, TUpdateEnvDTO } from "./project-env-types";
|
import { TCreateEnvDTO, TDeleteEnvDTO, TUpdateEnvDTO } from "./project-env-types";
|
||||||
|
|
||||||
type TProjectEnvServiceFactoryDep = {
|
type TProjectEnvServiceFactoryDep = {
|
||||||
projectEnvDal: TProjectEnvDalFactory;
|
projectEnvDal: TProjectEnvDalFactory;
|
||||||
|
projectDal: Pick<TProjectDalFactory, "findById">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TProjectEnvServiceFactory = ReturnType<typeof projectEnvServiceFactory>;
|
export type TProjectEnvServiceFactory = ReturnType<typeof projectEnvServiceFactory>;
|
||||||
|
|
||||||
export const projectEnvServiceFactory = ({
|
export const projectEnvServiceFactory = ({
|
||||||
projectEnvDal,
|
projectEnvDal,
|
||||||
permissionService
|
permissionService,
|
||||||
|
licenseService,
|
||||||
|
projectDal
|
||||||
}: TProjectEnvServiceFactoryDep) => {
|
}: TProjectEnvServiceFactoryDep) => {
|
||||||
const createEnvironment = async ({ projectId, actorId, actor, name, slug }: TCreateEnvDTO) => {
|
const createEnvironment = async ({ projectId, actorId, actor, name, slug }: TCreateEnvDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||||
@@ -28,14 +34,25 @@ export const projectEnvServiceFactory = ({
|
|||||||
ProjectPermissionSub.Environments
|
ProjectPermissionSub.Environments
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO(akhilmhdh-pg): add licence service here
|
const envs = await projectEnvDal.find({ projectId });
|
||||||
const existingEnv = await projectEnvDal.findOne({ slug });
|
const existingEnv = envs.find(({ slug: envSlug }) => envSlug === slug);
|
||||||
if (existingEnv)
|
if (existingEnv)
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Environment with slug already exist",
|
message: "Environment with slug already exist",
|
||||||
name: "Create envv"
|
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 env = await projectEnvDal.transaction(async (tx) => {
|
||||||
const lastPos = await projectEnvDal.findLastEnvPosition(projectId, tx);
|
const lastPos = await projectEnvDal.findLastEnvPosition(projectId, tx);
|
||||||
const doc = await projectEnvDal.create({ slug, name, projectId, position: lastPos + 1 }, tx);
|
const doc = await projectEnvDal.create({ slug, name, projectId, position: lastPos + 1 }, tx);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { OrgMembershipStatus, ProjectMembershipRole } from "@app/db/schemas";
|
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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -34,6 +35,7 @@ type TProjectMembershipServiceFactoryDep = {
|
|||||||
orgDal: Pick<TOrgDalFactory, "findMembership">;
|
orgDal: Pick<TOrgDalFactory, "findMembership">;
|
||||||
projectDal: Pick<TProjectDalFactory, "findById">;
|
projectDal: Pick<TProjectDalFactory, "findById">;
|
||||||
projectKeyDal: Pick<TProjectKeyDalFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
projectKeyDal: Pick<TProjectKeyDalFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TProjectMembershipServiceFactory = ReturnType<typeof projectMembershipServiceFactory>;
|
export type TProjectMembershipServiceFactory = ReturnType<typeof projectMembershipServiceFactory>;
|
||||||
@@ -46,7 +48,8 @@ export const projectMembershipServiceFactory = ({
|
|||||||
orgDal,
|
orgDal,
|
||||||
userDal,
|
userDal,
|
||||||
projectDal,
|
projectDal,
|
||||||
projectKeyDal
|
projectKeyDal,
|
||||||
|
licenseService
|
||||||
}: TProjectMembershipServiceFactoryDep) => {
|
}: TProjectMembershipServiceFactoryDep) => {
|
||||||
const getProjectMemberships = async ({ actorId, actor, projectId }: TGetProjectMembershipDTO) => {
|
const getProjectMemberships = async ({ actorId, actor, projectId }: TGetProjectMembershipDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||||
@@ -120,7 +123,6 @@ export const projectMembershipServiceFactory = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(akhilmhdh-pg): Audit log
|
|
||||||
return { invitee, latestKey };
|
return { invitee, latestKey };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -209,6 +211,14 @@ export const projectMembershipServiceFactory = ({
|
|||||||
const customRole = await projectRoleDal.findOne({ slug: role, projectId });
|
const customRole = await projectRoleDal.findOne({ slug: role, projectId });
|
||||||
if (!customRole)
|
if (!customRole)
|
||||||
throw new BadRequestError({ name: "Update project membership", message: "Role not found" });
|
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(
|
const [membership] = await projectMembershipDal.update(
|
||||||
{ id: membershipId, projectId },
|
{ id: membershipId, projectId },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ProjectMembershipRole } from "@app/db/schemas";
|
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects
|
OrgPermissionSubjects
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { createSecretBlindIndex } from "@app/lib/crypto";
|
import { createSecretBlindIndex } from "@app/lib/crypto";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TProjectEnvDalFactory } from "../project-env/project-env-dal";
|
import { TProjectEnvDalFactory } from "../project-env/project-env-dal";
|
||||||
import { TProjectMembershipDalFactory } from "../project-membership/project-membership-dal";
|
import { TProjectMembershipDalFactory } from "../project-membership/project-membership-dal";
|
||||||
@@ -33,6 +35,7 @@ type TProjectServiceFactoryDep = {
|
|||||||
projectMembershipDal: Pick<TProjectMembershipDalFactory, "create">;
|
projectMembershipDal: Pick<TProjectMembershipDalFactory, "create">;
|
||||||
secretBlindIndexDal: Pick<TSecretBlindIndexDalFactory, "create">;
|
secretBlindIndexDal: Pick<TSecretBlindIndexDalFactory, "create">;
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TProjectServiceFactory = ReturnType<typeof projectServiceFactory>;
|
export type TProjectServiceFactory = ReturnType<typeof projectServiceFactory>;
|
||||||
@@ -43,7 +46,8 @@ export const projectServiceFactory = ({
|
|||||||
folderDal,
|
folderDal,
|
||||||
secretBlindIndexDal,
|
secretBlindIndexDal,
|
||||||
projectMembershipDal,
|
projectMembershipDal,
|
||||||
projectEnvDal
|
projectEnvDal,
|
||||||
|
licenseService
|
||||||
}: TProjectServiceFactoryDep) => {
|
}: TProjectServiceFactoryDep) => {
|
||||||
/*
|
/*
|
||||||
* Create workspace. Make user the admin
|
* Create workspace. Make user the admin
|
||||||
@@ -57,7 +61,17 @@ export const projectServiceFactory = ({
|
|||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const blindIndex = createSecretBlindIndex(appCfg.ROOT_ENCRYPTION_KEY, appCfg.ENCRYPTION_KEY);
|
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 newProject = projectDal.transaction(async (tx) => {
|
||||||
const project = await projectDal.create({ name: workspaceName, orgId }, tx);
|
const project = await projectDal.create({ name: workspaceName, orgId }, tx);
|
||||||
// set user as admin member for proeject
|
// set user as admin member for proeject
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ export const serviceTokenServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const token = `st.${serviceToken.id.toString()}.${secret}`;
|
const token = `st.${serviceToken.id.toString()}.${secret}`;
|
||||||
// TODO(akhilmhdh-pg): audit log
|
|
||||||
|
|
||||||
return { token, serviceToken };
|
return { token, serviceToken };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ export const webhookServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const webhook = await webhookDal.create(insertDoc);
|
const webhook = await webhookDal.create(insertDoc);
|
||||||
// TODO(akhilmhdh-pg): add audit log
|
|
||||||
return { ...webhook, projectId, environment: env };
|
return { ...webhook, projectId, environment: env };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export type TrustedIp = {
|
export type TrustedIp = {
|
||||||
id: string;
|
id: string;
|
||||||
workspace: string;
|
projectId: string;
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
type: "ipv4" | "ipv6";
|
type: "ipv4" | "ipv6";
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|||||||
+1
-1
@@ -172,7 +172,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
|
|||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
filterdUser?.map(
|
filterdUser?.map(
|
||||||
({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status }) => {
|
({ 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;
|
const email = u?.email || inviteEmail;
|
||||||
return (
|
return (
|
||||||
<Tr key={`org-membership-${orgMembershipId}`} className="w-full">
|
<Tr key={`org-membership-${orgMembershipId}`} className="w-full">
|
||||||
|
|||||||
Reference in New Issue
Block a user