mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 10:29:17 +00:00
Merge remote-tracking branch 'origin' into checkly-sync-on-group-level
This commit is contained in:
Generated
+1177
-1370
File diff suppressed because it is too large
Load Diff
+12
-6
@@ -6,7 +6,7 @@
|
|||||||
"@godaddy/terminus": "^4.12.0",
|
"@godaddy/terminus": "^4.12.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@octokit/rest": "^19.0.5",
|
"@octokit/rest": "^19.0.5",
|
||||||
"@sentry/node": "^7.49.0",
|
"@sentry/node": "^7.77.0",
|
||||||
"@sentry/tracing": "^7.48.0",
|
"@sentry/tracing": "^7.48.0",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/libsodium-wrappers": "^0.7.10",
|
"@types/libsodium-wrappers": "^0.7.10",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"bigint-conversion": "^2.4.0",
|
"bigint-conversion": "^2.4.0",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.2.0",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"express": "^4.18.1",
|
"express": "^4.18.1",
|
||||||
"express-async-errors": "^3.1.1",
|
"express-async-errors": "^3.1.1",
|
||||||
@@ -34,7 +34,6 @@
|
|||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
"libsodium-wrappers": "^0.7.10",
|
"libsodium-wrappers": "^0.7.10",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mongodb": "^5.7.0",
|
|
||||||
"mongoose": "^7.4.1",
|
"mongoose": "^7.4.1",
|
||||||
"nanoid": "^3.3.6",
|
"nanoid": "^3.3.6",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
@@ -43,6 +42,8 @@
|
|||||||
"passport-github": "^1.1.0",
|
"passport-github": "^1.1.0",
|
||||||
"passport-gitlab2": "^5.0.0",
|
"passport-gitlab2": "^5.0.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
|
"pino": "^8.16.1",
|
||||||
|
"pino-http": "^8.5.1",
|
||||||
"posthog-node": "^2.6.0",
|
"posthog-node": "^2.6.0",
|
||||||
"probot": "^12.3.1",
|
"probot": "^12.3.1",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
@@ -53,16 +54,19 @@
|
|||||||
"tweetnacl-util": "^0.15.1",
|
"tweetnacl-util": "^0.15.1",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
"utility-types": "^3.10.0",
|
"utility-types": "^3.10.0",
|
||||||
"winston": "^3.8.2",
|
|
||||||
"winston-loki": "^6.0.6",
|
|
||||||
"zod": "^3.22.3"
|
"zod": "^3.22.3"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"rate-limit-mongo": {
|
||||||
|
"mongodb": "5.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"name": "infisical-api",
|
"name": "infisical-api",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node build/index.js",
|
"start": "node build/index.js",
|
||||||
"dev": "nodemon",
|
"dev": "nodemon index.js | pino-pretty --colorize",
|
||||||
"swagger-autogen": "node ./swagger/index.ts",
|
"swagger-autogen": "node ./swagger/index.ts",
|
||||||
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build",
|
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build",
|
||||||
"lint": "eslint . --ext .ts",
|
"lint": "eslint . --ext .ts",
|
||||||
@@ -100,6 +104,7 @@
|
|||||||
"@types/nodemailer": "^6.4.6",
|
"@types/nodemailer": "^6.4.6",
|
||||||
"@types/passport": "^1.0.12",
|
"@types/passport": "^1.0.12",
|
||||||
"@types/picomatch": "^2.3.0",
|
"@types/picomatch": "^2.3.0",
|
||||||
|
"@types/pino": "^7.0.5",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/supertest": "^2.0.12",
|
||||||
"@types/swagger-jsdoc": "^6.0.1",
|
"@types/swagger-jsdoc": "^6.0.1",
|
||||||
"@types/swagger-ui-express": "^4.1.3",
|
"@types/swagger-ui-express": "^4.1.3",
|
||||||
@@ -113,6 +118,7 @@
|
|||||||
"jest-junit": "^15.0.0",
|
"jest-junit": "^15.0.0",
|
||||||
"nodemon": "^2.0.19",
|
"nodemon": "^2.0.19",
|
||||||
"npm": "^8.19.3",
|
"npm": "^8.19.3",
|
||||||
|
"pino-pretty": "^10.2.3",
|
||||||
"smee-client": "^1.2.3",
|
"smee-client": "^1.2.3",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"swagger-autogen": "^2.23.5",
|
"swagger-autogen": "^2.23.5",
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
} from "../../ee/services/ProjectRoleService";
|
} from "../../ee/services/ProjectRoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { getIntegrationAuthAccessHelper } from "../../helpers";
|
import { getIntegrationAuthAccessHelper } from "../../helpers";
|
||||||
import { ObjectId } from "mongodb";
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Return integration authorization with id [integrationAuthId]
|
* Return integration authorization with id [integrationAuthId]
|
||||||
@@ -256,7 +255,7 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -294,7 +293,7 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -330,7 +329,7 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -391,7 +390,7 @@ export const getIntegrationAuthQoveryOrgs = async (req: Request, res: Response)
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -443,7 +442,7 @@ export const getIntegrationAuthQoveryProjects = async (req: Request, res: Respon
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -504,7 +503,7 @@ export const getIntegrationAuthQoveryEnvironments = async (req: Request, res: Re
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -565,7 +564,7 @@ export const getIntegrationAuthQoveryApps = async (req: Request, res: Response)
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -626,7 +625,7 @@ export const getIntegrationAuthQoveryContainers = async (req: Request, res: Resp
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -687,7 +686,7 @@ export const getIntegrationAuthQoveryJobs = async (req: Request, res: Response)
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -749,7 +748,7 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -842,7 +841,7 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -966,7 +965,7 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -1022,7 +1021,7 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -1110,7 +1109,7 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
@@ -1179,7 +1178,7 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: new ObjectId(integrationAuthId)
|
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await getUserProjectPermissions(
|
const { permission } = await getUserProjectPermissions(
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { client, getRootEncryptionKey } from "../../config";
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
|
||||||
import { Webhook } from "../../models";
|
import { Webhook } from "../../models";
|
||||||
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
|
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
|
||||||
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
|
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
|
||||||
import { EEAuditLogService } from "../../ee/services";
|
import { EEAuditLogService } from "../../ee/services";
|
||||||
import { EventType } from "../../ee/models";
|
import { EventType } from "../../ee/models";
|
||||||
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64 } from "../../variables";
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_BASE64,
|
||||||
|
ENCODING_SCHEME_UTF8
|
||||||
|
} from "../../variables";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
import * as reqValidator from "../../validation/webhooks";
|
import * as reqValidator from "../../validation/webhooks";
|
||||||
import {
|
import {
|
||||||
@@ -15,6 +19,7 @@ import {
|
|||||||
getUserProjectPermissions
|
getUserProjectPermissions
|
||||||
} from "../../ee/services/ProjectRoleService";
|
} from "../../ee/services/ProjectRoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
||||||
|
|
||||||
export const createWebhook = async (req: Request, res: Response) => {
|
export const createWebhook = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
@@ -31,17 +36,31 @@ export const createWebhook = async (req: Request, res: Response) => {
|
|||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
url: webhookUrl,
|
url: webhookUrl
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
|
||||||
keyEncoding: ENCODING_SCHEME_BASE64
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (webhookSecretKey) {
|
if (webhookSecretKey) {
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
const rootEncryptionKey = await getRootEncryptionKey();
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey);
|
|
||||||
webhook.iv = iv;
|
if (rootEncryptionKey) {
|
||||||
webhook.tag = tag;
|
const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey);
|
||||||
webhook.encryptedSecretKey = ciphertext;
|
webhook.iv = iv;
|
||||||
|
webhook.tag = tag;
|
||||||
|
webhook.encryptedSecretKey = ciphertext;
|
||||||
|
webhook.algorithm = ALGORITHM_AES_256_GCM;
|
||||||
|
webhook.keyEncoding = ENCODING_SCHEME_BASE64;
|
||||||
|
} else if (encryptionKey) {
|
||||||
|
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: webhookSecretKey,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
webhook.iv = iv;
|
||||||
|
webhook.tag = tag;
|
||||||
|
webhook.encryptedSecretKey = ciphertext;
|
||||||
|
webhook.algorithm = ALGORITHM_AES_256_GCM;
|
||||||
|
webhook.keyEncoding = ENCODING_SCHEME_UTF8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await webhook.save();
|
await webhook.save();
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
ValidationError as RouteValidationError,
|
ValidationError as RouteValidationError,
|
||||||
UnauthorizedRequestError
|
UnauthorizedRequestError
|
||||||
} from "../../utils/errors";
|
} from "../../utils/errors";
|
||||||
import { AnyBulkWriteOperation } from "mongodb";
|
|
||||||
import {
|
import {
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
@@ -19,7 +18,7 @@ import {
|
|||||||
SECRET_SHARED
|
SECRET_SHARED
|
||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
import { TelemetryService } from "../../services";
|
import { TelemetryService } from "../../services";
|
||||||
import { ISecret, Secret, User } from "../../models";
|
import { Secret, User } from "../../models";
|
||||||
import { AccountNotFoundError } from "../../utils/errors";
|
import { AccountNotFoundError } from "../../utils/errors";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,22 +144,22 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
const secretsUserCanDeleteSet: Set<string> = new Set(
|
const secretsUserCanDeleteSet: Set<string> = new Set(
|
||||||
secretIdsUserCanDelete.map((objectId) => objectId._id.toString())
|
secretIdsUserCanDelete.map((objectId) => objectId._id.toString())
|
||||||
);
|
);
|
||||||
const deleteOperationsToPerform: AnyBulkWriteOperation<ISecret>[] = [];
|
|
||||||
|
|
||||||
let numSecretsDeleted = 0;
|
// Filter out IDs that user can delete and then map them to delete operations
|
||||||
secretIdsToDelete.forEach((secretIdToDelete) => {
|
const deleteOperationsToPerform = secretIdsToDelete
|
||||||
if (secretsUserCanDeleteSet.has(secretIdToDelete)) {
|
.filter(secretIdToDelete => {
|
||||||
const deleteOperation = {
|
if (!secretsUserCanDeleteSet.has(secretIdToDelete)) {
|
||||||
deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } }
|
throw RouteValidationError({
|
||||||
};
|
message: "You cannot delete secrets that you do not have access to"
|
||||||
deleteOperationsToPerform.push(deleteOperation);
|
});
|
||||||
numSecretsDeleted++;
|
}
|
||||||
} else {
|
return true;
|
||||||
throw RouteValidationError({
|
})
|
||||||
message: "You cannot delete secrets that you do not have access to"
|
.map(secretIdToDelete => ({
|
||||||
});
|
deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } }
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
|
const numSecretsDeleted = deleteOperationsToPerform.length;
|
||||||
|
|
||||||
await Secret.bulkWrite(deleteOperationsToPerform);
|
await Secret.bulkWrite(deleteOperationsToPerform);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { getLogger } from "../utils/logger";
|
import { logger } from "../utils/logging";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize database connection
|
* Initialize database connection
|
||||||
@@ -18,10 +18,10 @@ export const initDatabaseHelper = async ({
|
|||||||
// allow empty strings to pass the required validator
|
// allow empty strings to pass the required validator
|
||||||
mongoose.Schema.Types.String.checkRequired(v => typeof v === "string");
|
mongoose.Schema.Types.String.checkRequired(v => typeof v === "string");
|
||||||
|
|
||||||
(await getLogger("database")).info("Database connection established");
|
logger.info("Database connection established");
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
(await getLogger("database")).error(`Unable to establish Database connection due to the error.\n${err}`);
|
logger.error(err, "Unable to establish database connection");
|
||||||
}
|
}
|
||||||
|
|
||||||
return mongoose.connection;
|
return mongoose.connection;
|
||||||
|
|||||||
+204
-300
@@ -1,4 +1,4 @@
|
|||||||
import mongoose, { Types, mongo } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
Bot,
|
Bot,
|
||||||
BotKey,
|
BotKey,
|
||||||
@@ -55,7 +55,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
createBotOrg
|
createBotOrg
|
||||||
} from "./botOrg";
|
} from "./botOrg";
|
||||||
import { InternalServerError, ResourceNotFoundError } from "../utils/errors";
|
import { ResourceNotFoundError } from "../utils/errors";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an organization with name [name]
|
* Create an organization with name [name]
|
||||||
@@ -111,311 +111,215 @@ export const createOrganization = async ({
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteOrganization = async ({
|
export const deleteOrganization = async ({
|
||||||
organizationId,
|
organizationId
|
||||||
existingSession
|
|
||||||
}: {
|
}: {
|
||||||
organizationId: Types.ObjectId;
|
organizationId: Types.ObjectId;
|
||||||
existingSession?: mongo.ClientSession;
|
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
let session;
|
const organization = await Organization.findByIdAndDelete(
|
||||||
|
organizationId
|
||||||
if (existingSession) {
|
);
|
||||||
session = existingSession;
|
|
||||||
} else {
|
|
||||||
session = await mongoose.startSession();
|
|
||||||
session.startTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (!organization) throw ResourceNotFoundError();
|
||||||
const organization = await Organization.findByIdAndDelete(
|
|
||||||
organizationId,
|
await MembershipOrg.deleteMany({
|
||||||
{
|
organization: organization._id
|
||||||
session
|
});
|
||||||
}
|
|
||||||
|
await BotOrg.deleteMany({
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await SSOConfig.deleteMany({
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await Role.deleteMany({
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await IncidentContactOrg.deleteMany({
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await GitRisks.deleteMany({
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await GitAppInstallationSession.deleteMany({
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await GitAppOrganizationInstallation.deleteMany({
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
const workspaceIds = await Workspace.distinct("_id", {
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await Workspace.deleteMany({
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await Membership.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Key.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Bot.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await BotKey.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await SecretBlindIndexData.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Secret.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await SecretVersion.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await SecretSnapshot.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await SecretImport.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Folder.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await FolderVersion.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Webhook.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await TrustedIP.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Tag.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await IntegrationAuth.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Integration.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await ServiceToken.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await ServiceTokenData.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await ServiceTokenDataV3.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await ServiceTokenDataV3Key.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await AuditLog.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Log.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Action.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await SecretApprovalPolicy.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await SecretApprovalRequest.deleteMany({
|
||||||
|
workspace: {
|
||||||
|
$in: workspaceIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (organization.customerId) {
|
||||||
|
// delete from stripe here
|
||||||
|
await licenseServerKeyRequest.delete(
|
||||||
|
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!organization) throw ResourceNotFoundError();
|
|
||||||
|
|
||||||
await MembershipOrg.deleteMany({
|
|
||||||
organization: organization._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await BotOrg.deleteMany({
|
|
||||||
organization: organization._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SSOConfig.deleteMany({
|
|
||||||
organization: organization._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Role.deleteMany({
|
|
||||||
organization: organization._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await IncidentContactOrg.deleteMany({
|
|
||||||
organization: organization._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await GitRisks.deleteMany({
|
|
||||||
organization: organization._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await GitAppInstallationSession.deleteMany({
|
|
||||||
organization: organization._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await GitAppOrganizationInstallation.deleteMany({
|
|
||||||
organization: organization._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
const workspaceIds = await Workspace.distinct("_id", {
|
|
||||||
organization: organization._id
|
|
||||||
});
|
|
||||||
|
|
||||||
await Workspace.deleteMany({
|
|
||||||
organization: organization._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Membership.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Key.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Bot.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await BotKey.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretBlindIndexData.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Secret.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretVersion.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretSnapshot.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretImport.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Folder.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await FolderVersion.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Webhook.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await TrustedIP.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Tag.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await IntegrationAuth.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Integration.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceToken.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceTokenData.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceTokenDataV3.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceTokenDataV3Key.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await AuditLog.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Log.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Action.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretApprovalPolicy.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretApprovalRequest.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
if (organization.customerId) {
|
|
||||||
// delete from stripe here
|
|
||||||
await licenseServerKeyRequest.delete(
|
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return organization;
|
|
||||||
} catch (err) {
|
|
||||||
if (!existingSession) {
|
|
||||||
await session.abortTransaction();
|
|
||||||
}
|
|
||||||
throw InternalServerError({
|
|
||||||
message: "Failed to delete organization"
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
if (!existingSession) {
|
|
||||||
await session.commitTransaction();
|
|
||||||
session.endSession();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return organization;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+76
-125
@@ -1,4 +1,4 @@
|
|||||||
import mongoose, { Types, mongo } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
APIKeyData,
|
APIKeyData,
|
||||||
BackupPrivateKey,
|
BackupPrivateKey,
|
||||||
@@ -222,141 +222,92 @@ const checkDeleteUserConditions = async ({
|
|||||||
* @returns {User} user - deleted user
|
* @returns {User} user - deleted user
|
||||||
*/
|
*/
|
||||||
export const deleteUser = async ({
|
export const deleteUser = async ({
|
||||||
userId,
|
userId
|
||||||
existingSession
|
|
||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId;
|
userId: Types.ObjectId;
|
||||||
existingSession?: mongo.ClientSession;
|
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
|
const user = await User.findByIdAndDelete(userId);
|
||||||
|
|
||||||
let session;
|
if (!user) throw ResourceNotFoundError();
|
||||||
|
|
||||||
|
await checkDeleteUserConditions({
|
||||||
|
userId: user._id
|
||||||
|
});
|
||||||
|
|
||||||
if (existingSession) {
|
await UserAction.deleteMany({
|
||||||
session = existingSession;
|
user: user._id
|
||||||
} else {
|
});
|
||||||
session = await mongoose.startSession();
|
|
||||||
session.startTransaction();
|
await BackupPrivateKey.deleteMany({
|
||||||
|
user: user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await APIKeyData.deleteMany({
|
||||||
|
user: user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await Action.deleteMany({
|
||||||
|
user: user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await Log.deleteMany({
|
||||||
|
user: user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await TokenVersion.deleteMany({
|
||||||
|
user: user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await Key.deleteMany({
|
||||||
|
receiver: user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
const membershipOrgs = await MembershipOrg.find({
|
||||||
|
user: userId
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete organizations where user is only member
|
||||||
|
for await (const membershipOrg of membershipOrgs) {
|
||||||
|
const memberCount = await MembershipOrg.countDocuments({
|
||||||
|
organization: membershipOrg.organization
|
||||||
|
});
|
||||||
|
|
||||||
|
if (memberCount === 1) {
|
||||||
|
// organization only has 1 member (the current user)
|
||||||
|
|
||||||
|
await deleteOrganization({
|
||||||
|
organizationId: membershipOrg.organization
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const memberships = await Membership.find({
|
||||||
const user = await User.findByIdAndDelete(userId, {
|
user: userId
|
||||||
session
|
});
|
||||||
|
|
||||||
|
// delete workspaces where user is only member
|
||||||
|
for await (const membership of memberships) {
|
||||||
|
const memberCount = await Membership.countDocuments({
|
||||||
|
workspace: membership.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) throw ResourceNotFoundError();
|
if (memberCount === 1) {
|
||||||
|
// workspace only has 1 member (the current user) -> delete workspace
|
||||||
|
|
||||||
await checkDeleteUserConditions({
|
await deleteWorkspace({
|
||||||
userId: user._id
|
workspaceId: membership.workspace
|
||||||
});
|
|
||||||
|
|
||||||
await UserAction.deleteMany({
|
|
||||||
user: user._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await BackupPrivateKey.deleteMany({
|
|
||||||
user: user._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await APIKeyData.deleteMany({
|
|
||||||
user: user._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Action.deleteMany({
|
|
||||||
user: user._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Log.deleteMany({
|
|
||||||
user: user._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await TokenVersion.deleteMany({
|
|
||||||
user: user._id
|
|
||||||
});
|
|
||||||
|
|
||||||
await Key.deleteMany({
|
|
||||||
receiver: user._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
const membershipOrgs = await MembershipOrg.find({
|
|
||||||
user: userId
|
|
||||||
}, null, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
// delete organizations where user is only member
|
|
||||||
for await (const membershipOrg of membershipOrgs) {
|
|
||||||
const memberCount = await MembershipOrg.countDocuments({
|
|
||||||
organization: membershipOrg.organization
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (memberCount === 1) {
|
|
||||||
// organization only has 1 member (the current user)
|
|
||||||
|
|
||||||
await deleteOrganization({
|
|
||||||
organizationId: membershipOrg.organization,
|
|
||||||
existingSession: session
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const memberships = await Membership.find({
|
|
||||||
user: userId
|
|
||||||
}, null, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
// delete workspaces where user is only member
|
|
||||||
for await (const membership of memberships) {
|
|
||||||
const memberCount = await Membership.countDocuments({
|
|
||||||
workspace: membership.workspace
|
|
||||||
});
|
|
||||||
|
|
||||||
if (memberCount === 1) {
|
|
||||||
// workspace only has 1 member (the current user) -> delete workspace
|
|
||||||
|
|
||||||
await deleteWorkspace({
|
|
||||||
workspaceId: membership.workspace,
|
|
||||||
existingSession: session
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await MembershipOrg.deleteMany({
|
|
||||||
user: userId
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Membership.deleteMany({
|
|
||||||
user: userId
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
return user;
|
|
||||||
} catch (err) {
|
|
||||||
if (!existingSession) {
|
|
||||||
await session.abortTransaction();
|
|
||||||
}
|
|
||||||
throw InternalServerError({
|
|
||||||
message: "Failed to delete account"
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
if (!existingSession) {
|
|
||||||
await session.commitTransaction();
|
|
||||||
session.endSession();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await MembershipOrg.deleteMany({
|
||||||
|
user: userId
|
||||||
|
});
|
||||||
|
|
||||||
|
await Membership.deleteMany({
|
||||||
|
user: userId
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import mongoose, { Types, mongo } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
Bot,
|
Bot,
|
||||||
BotKey,
|
BotKey,
|
||||||
@@ -33,8 +33,7 @@ import {
|
|||||||
import { createBot } from "../helpers/bot";
|
import { createBot } from "../helpers/bot";
|
||||||
import { EELicenseService } from "../ee/services";
|
import { EELicenseService } from "../ee/services";
|
||||||
import { SecretService } from "../services";
|
import { SecretService } from "../services";
|
||||||
import {
|
import {
|
||||||
InternalServerError,
|
|
||||||
ResourceNotFoundError
|
ResourceNotFoundError
|
||||||
} from "../utils/errors";
|
} from "../utils/errors";
|
||||||
|
|
||||||
@@ -102,189 +101,113 @@ export const createWorkspace = async ({
|
|||||||
* @param {String} obj.id - id of workspace to delete
|
* @param {String} obj.id - id of workspace to delete
|
||||||
*/
|
*/
|
||||||
export const deleteWorkspace = async ({
|
export const deleteWorkspace = async ({
|
||||||
workspaceId,
|
workspaceId
|
||||||
existingSession
|
|
||||||
}: {
|
}: {
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
existingSession?: mongo.ClientSession;
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const workspace = await Workspace.findByIdAndDelete(workspaceId);
|
||||||
let session;
|
|
||||||
|
|
||||||
if (existingSession) {
|
|
||||||
session = existingSession;
|
|
||||||
} else {
|
|
||||||
session = await mongoose.startSession();
|
|
||||||
session.startTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (!workspace) throw ResourceNotFoundError();
|
||||||
const workspace = await Workspace.findByIdAndDelete(workspaceId, { session });
|
|
||||||
|
await Membership.deleteMany({
|
||||||
if (!workspace) throw ResourceNotFoundError();
|
workspace: workspace._id
|
||||||
|
});
|
||||||
await Membership.deleteMany({
|
|
||||||
workspace: workspace._id
|
await Key.deleteMany({
|
||||||
}, {
|
workspace: workspace._id
|
||||||
session
|
});
|
||||||
});
|
|
||||||
|
await Bot.deleteMany({
|
||||||
await Key.deleteMany({
|
workspace: workspace._id
|
||||||
workspace: workspace._id
|
});
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Bot.deleteMany({
|
|
||||||
workspace: workspace._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await BotKey.deleteMany({
|
await BotKey.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretBlindIndexData.deleteMany({
|
await SecretBlindIndexData.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Secret.deleteMany({
|
await Secret.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
await SecretVersion.deleteMany({
|
||||||
|
workspace: workspace._id
|
||||||
await SecretVersion.deleteMany({
|
});
|
||||||
workspace: workspace._id
|
|
||||||
}, {
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretSnapshot.deleteMany({
|
await SecretSnapshot.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretImport.deleteMany({
|
await SecretImport.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Folder.deleteMany({
|
await Folder.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await FolderVersion.deleteMany({
|
await FolderVersion.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Webhook.deleteMany({
|
await Webhook.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await TrustedIP.deleteMany({
|
await TrustedIP.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Tag.deleteMany({
|
await Tag.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await IntegrationAuth.deleteMany({
|
await IntegrationAuth.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Integration.deleteMany({
|
await Integration.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceToken.deleteMany({
|
await ServiceToken.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceTokenData.deleteMany({
|
await ServiceTokenData.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceTokenDataV3.deleteMany({
|
await ServiceTokenDataV3.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceTokenDataV3Key.deleteMany({
|
await ServiceTokenDataV3Key.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await AuditLog.deleteMany({
|
await AuditLog.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Log.deleteMany({
|
await Log.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await Action.deleteMany({
|
await Action.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretApprovalPolicy.deleteMany({
|
await SecretApprovalPolicy.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretApprovalRequest.deleteMany({
|
await SecretApprovalRequest.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
}, {
|
});
|
||||||
session
|
|
||||||
});
|
return workspace;
|
||||||
|
|
||||||
return workspace;
|
|
||||||
} catch (err) {
|
|
||||||
if (!existingSession) {
|
|
||||||
await session.abortTransaction();
|
|
||||||
}
|
|
||||||
throw InternalServerError({
|
|
||||||
message: "Failed to delete organization"
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
if (!existingSession) {
|
|
||||||
await session.commitTransaction();
|
|
||||||
session.endSession();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
+14
-4
@@ -5,6 +5,8 @@ import express from "express";
|
|||||||
require("express-async-errors");
|
require("express-async-errors");
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
|
import { logger } from "./utils/logging";
|
||||||
|
import httpLogger from "pino-http";
|
||||||
import { DatabaseService } from "./services";
|
import { DatabaseService } from "./services";
|
||||||
import { EELicenseService, GithubSecretScanningService } from "./ee/services";
|
import { EELicenseService, GithubSecretScanningService } from "./ee/services";
|
||||||
import { setUpHealthEndpoint } from "./services/health";
|
import { setUpHealthEndpoint } from "./services/health";
|
||||||
@@ -73,7 +75,7 @@ import {
|
|||||||
workspaces as v3WorkspacesRouter
|
workspaces as v3WorkspacesRouter
|
||||||
} from "./routes/v3";
|
} from "./routes/v3";
|
||||||
import { healthCheck } from "./routes/status";
|
import { healthCheck } from "./routes/status";
|
||||||
import { getLogger } from "./utils/logger";
|
// import { getLogger } from "./utils/logger";
|
||||||
import { RouteNotFoundError } from "./utils/errors";
|
import { RouteNotFoundError } from "./utils/errors";
|
||||||
import { requestErrorHandler } from "./middleware/requestErrorHandler";
|
import { requestErrorHandler } from "./middleware/requestErrorHandler";
|
||||||
import {
|
import {
|
||||||
@@ -94,12 +96,20 @@ import path from "path";
|
|||||||
let handler: null | any = null;
|
let handler: null | any = null;
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
|
const port = await getPort();
|
||||||
|
|
||||||
await setup();
|
await setup();
|
||||||
|
|
||||||
await EELicenseService.initGlobalFeatureSet();
|
await EELicenseService.initGlobalFeatureSet();
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.enable("trust proxy");
|
app.enable("trust proxy");
|
||||||
|
|
||||||
|
app.use(httpLogger({
|
||||||
|
logger,
|
||||||
|
autoLogging: false
|
||||||
|
}));
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: false }));
|
app.use(express.urlencoded({ extended: false }));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
@@ -164,7 +174,7 @@ const main = async () => {
|
|||||||
const nextApp = new NextServer({
|
const nextApp = new NextServer({
|
||||||
dev: false,
|
dev: false,
|
||||||
dir: nextJsBuildPath,
|
dir: nextJsBuildPath,
|
||||||
port: await getPort(),
|
port,
|
||||||
conf,
|
conf,
|
||||||
hostname: "local",
|
hostname: "local",
|
||||||
customServer: false
|
customServer: false
|
||||||
@@ -255,8 +265,8 @@ const main = async () => {
|
|||||||
|
|
||||||
app.use(requestErrorHandler);
|
app.use(requestErrorHandler);
|
||||||
|
|
||||||
const server = app.listen(await getPort(), async () => {
|
const server = app.listen(port, async () => {
|
||||||
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`);
|
logger.info(`Server started listening at port ${port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// await createTestUserForDevelopment();
|
// await createTestUserForDevelopment();
|
||||||
|
|||||||
@@ -2,49 +2,45 @@ import * as Sentry from "@sentry/node";
|
|||||||
import { ErrorRequestHandler } from "express";
|
import { ErrorRequestHandler } from "express";
|
||||||
import { TokenExpiredError } from "jsonwebtoken";
|
import { TokenExpiredError } from "jsonwebtoken";
|
||||||
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
|
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
|
||||||
import { getLogger } from "../utils/logger";
|
import { logger } from "../utils/logging";
|
||||||
import RequestError from "../utils/requestError";
|
import RequestError, { mapToPinoLogLevel } from "../utils/requestError";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
export const requestErrorHandler: ErrorRequestHandler = async (
|
export const requestErrorHandler: ErrorRequestHandler = async (
|
||||||
error: RequestError | Error,
|
err: RequestError | Error,
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
next
|
next
|
||||||
) => {
|
) => {
|
||||||
if (res.headersSent) return next();
|
if (res.headersSent) return next();
|
||||||
|
|
||||||
const logAndCaptureException = async (error: RequestError) => {
|
let error: RequestError;
|
||||||
(await getLogger("backend-main")).log(
|
|
||||||
(<RequestError>error).levelName.toLowerCase(),
|
switch (true) {
|
||||||
`${error.stack}\n${error.message}`
|
case err instanceof TokenExpiredError:
|
||||||
);
|
error = UnauthorizedRequestError({ stack: err.stack, message: "Token expired" });
|
||||||
|
break;
|
||||||
//* Set Sentry user identification if req.user is populated
|
case err instanceof ForbiddenError:
|
||||||
if (req.user !== undefined && req.user !== null) {
|
error = UnauthorizedRequestError({ context: { exception: err.message }, stack: err.stack })
|
||||||
Sentry.setUser({ email: (req.user as any).email });
|
break;
|
||||||
}
|
case err instanceof RequestError:
|
||||||
|
error = err as RequestError;
|
||||||
Sentry.captureException(error);
|
break;
|
||||||
};
|
default:
|
||||||
|
error = InternalServerError({ context: { exception: err.message }, stack: err.stack });
|
||||||
if (error instanceof RequestError) {
|
break;
|
||||||
if (error instanceof TokenExpiredError) {
|
|
||||||
error = UnauthorizedRequestError({ stack: error.stack, message: "Token expired" });
|
|
||||||
}
|
|
||||||
await logAndCaptureException((<RequestError>error));
|
|
||||||
} else {
|
|
||||||
if (error instanceof ForbiddenError) {
|
|
||||||
error = UnauthorizedRequestError({ context: { exception: error.message }, stack: error.stack })
|
|
||||||
} else {
|
|
||||||
error = InternalServerError({ context: { exception: error.message }, stack: error.stack });
|
|
||||||
}
|
|
||||||
|
|
||||||
await logAndCaptureException((<RequestError>error));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger[mapToPinoLogLevel(error.level)](error);
|
||||||
|
|
||||||
|
if (req.user) {
|
||||||
|
Sentry.setUser({ email: (req.user as any).email });
|
||||||
|
}
|
||||||
|
|
||||||
|
Sentry.captureException(error);
|
||||||
|
|
||||||
delete (<any>error).stacktrace // remove stack trace from being sent to client
|
delete (<any>error).stacktrace // remove stack trace from being sent to client
|
||||||
res.status((<RequestError>error).statusCode).json(error);
|
res.status((<RequestError>error).statusCode).json(error); // revise json part here
|
||||||
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,13 +65,11 @@ const WebhookSchema = new Schema<IWebhook>(
|
|||||||
// the encryption algorithm used
|
// the encryption algorithm used
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ALGORITHM_AES_256_GCM],
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
required: true,
|
|
||||||
select: false
|
select: false
|
||||||
},
|
},
|
||||||
keyEncoding: {
|
keyEncoding: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
|
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
|
||||||
required: true,
|
|
||||||
select: false
|
select: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -80,4 +78,4 @@ const WebhookSchema = new Schema<IWebhook>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Webhook = model<IWebhook>("Webhook", WebhookSchema);
|
export const Webhook = model<IWebhook>("Webhook", WebhookSchema);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PostHog } from "posthog-node";
|
import { PostHog } from "posthog-node";
|
||||||
import { getLogger } from "../utils/logger";
|
import { logger } from "../utils/logging";
|
||||||
import { AuthData } from "../interfaces/middleware";
|
import { AuthData } from "../interfaces/middleware";
|
||||||
import {
|
import {
|
||||||
getNodeEnv,
|
getNodeEnv,
|
||||||
@@ -22,13 +22,13 @@ class Telemetry {
|
|||||||
* Logs telemetry enable/disable notice.
|
* Logs telemetry enable/disable notice.
|
||||||
*/
|
*/
|
||||||
static logTelemetryMessage = async () => {
|
static logTelemetryMessage = async () => {
|
||||||
|
|
||||||
if(!(await getTelemetryEnabled())){
|
if(!(await getTelemetryEnabled())){
|
||||||
(await getLogger("backend-main")).info([
|
[
|
||||||
"",
|
|
||||||
"To improve, Infisical collects telemetry data about general usage.",
|
"To improve, Infisical collects telemetry data about general usage.",
|
||||||
"This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.",
|
"This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.",
|
||||||
"To opt into telemetry, you can set `TELEMETRY_ENABLED=true` within the environment variables.",
|
"To opt into telemetry, you can set `TELEMETRY_ENABLED=true` within the environment variables.",
|
||||||
].join("\n"))
|
].forEach(line => logger.info(line));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,26 +2,42 @@ import axios from "axios";
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
import { client, getRootEncryptionKey } from "../config";
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||||
import { IWebhook, Webhook } from "../models";
|
import { IWebhook, Webhook } from "../models";
|
||||||
|
import { decryptSymmetric128BitHexKeyUTF8 } from "../utils/crypto";
|
||||||
|
import { ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8 } from "../variables";
|
||||||
|
|
||||||
export const triggerWebhookRequest = async (
|
export const triggerWebhookRequest = async (
|
||||||
{ url, encryptedSecretKey, iv, tag }: IWebhook,
|
{ url, encryptedSecretKey, iv, tag, keyEncoding }: IWebhook,
|
||||||
payload: Record<string, unknown>
|
payload: Record<string, unknown>
|
||||||
) => {
|
) => {
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
payload["timestamp"] = Date.now();
|
payload["timestamp"] = Date.now();
|
||||||
|
|
||||||
if (encryptedSecretKey) {
|
if (encryptedSecretKey) {
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
const rootEncryptionKey = await getRootEncryptionKey();
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
const secretKey = client.decryptSymmetric(encryptedSecretKey, rootEncryptionKey, iv, tag);
|
let secretKey;
|
||||||
const webhookSign = crypto
|
if (rootEncryptionKey && keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||||
.createHmac("sha256", secretKey)
|
// case: encoding scheme is base64
|
||||||
.update(JSON.stringify(payload))
|
secretKey = client.decryptSymmetric(encryptedSecretKey, rootEncryptionKey, iv, tag);
|
||||||
.digest("hex");
|
} else if (encryptionKey && keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||||
headers["x-infisical-signature"] = `t=${payload["timestamp"]};${webhookSign}`;
|
// case: encoding scheme is utf8
|
||||||
|
secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: encryptedSecretKey,
|
||||||
|
iv: iv,
|
||||||
|
tag: tag,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (secretKey) {
|
||||||
|
const webhookSign = crypto
|
||||||
|
.createHmac("sha256", secretKey)
|
||||||
|
.update(JSON.stringify(payload))
|
||||||
|
.digest("hex");
|
||||||
|
headers["x-infisical-signature"] = `t=${payload["timestamp"]};${webhookSign}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = await axios.post(url, payload, { headers });
|
const req = await axios.post(url, payload, { headers });
|
||||||
return req;
|
return req;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { createTerminus } from "@godaddy/terminus";
|
import { createTerminus } from "@godaddy/terminus";
|
||||||
import { getLogger } from "../utils/logger";
|
import { logger } from "../utils/logging";
|
||||||
|
|
||||||
export const setUpHealthEndpoint = <T>(server: T) => {
|
export const setUpHealthEndpoint = <T>(server: T) => {
|
||||||
const onSignal = async () => {
|
const onSignal = async () => {
|
||||||
(await getLogger("backend-main")).info("Server is starting clean-up");
|
logger.info("Server is starting clean-up");
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
if (mongoose.connection && mongoose.connection.readyState == 1) {
|
if (mongoose.connection && mongoose.connection.readyState == 1) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
getSmtpSecure,
|
getSmtpSecure,
|
||||||
getSmtpUsername,
|
getSmtpUsername,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import { getLogger } from "../utils/logger";
|
import { logger } from "../utils/logging";
|
||||||
|
|
||||||
export const initSmtp = async () => {
|
export const initSmtp = async () => {
|
||||||
const mailOpts: SMTPConnection.Options = {
|
const mailOpts: SMTPConnection.Options = {
|
||||||
@@ -84,15 +84,14 @@ export const initSmtp = async () => {
|
|||||||
.then(async () => {
|
.then(async () => {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureMessage("SMTP - Successfully connected");
|
Sentry.captureMessage("SMTP - Successfully connected");
|
||||||
(await getLogger("backend-main")).info(
|
logger.info("SMTP - Successfully connected");
|
||||||
"SMTP - Successfully connected"
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch(async (err) => {
|
.catch(async (err) => {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(
|
Sentry.captureException(
|
||||||
`SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()} \n\t${err}`
|
`SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()} \n\t${err}`
|
||||||
);
|
);
|
||||||
|
logger.error(err, `SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
return transporter;
|
return transporter;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const UnauthorizedRequestError = (error?: Partial<RequestErrorContext>) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const ForbiddenRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
export const ForbiddenRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
logLevel: error?.logLevel ?? LogLevel.INFO,
|
logLevel: error?.logLevel ?? LogLevel.WARN,
|
||||||
statusCode: error?.statusCode ?? 403,
|
statusCode: error?.statusCode ?? 403,
|
||||||
type: error?.type ?? "forbidden",
|
type: error?.type ?? "forbidden",
|
||||||
message: error?.message ?? "You are not allowed to access this resource",
|
message: error?.message ?? "You are not allowed to access this resource",
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
/* eslint-disable no-console */
|
|
||||||
import { createLogger, format, transports } from "winston";
|
|
||||||
import LokiTransport from "winston-loki";
|
|
||||||
import { getLokiHost, getNodeEnv } from "../config";
|
|
||||||
|
|
||||||
const { combine, colorize, label, printf, splat, timestamp } = format;
|
|
||||||
|
|
||||||
const logFormat = (prefix: string) => combine(
|
|
||||||
timestamp(),
|
|
||||||
splat(),
|
|
||||||
label({ label: prefix }),
|
|
||||||
printf((info) => `${info.timestamp} ${info.label} ${info.level}: ${info.message}`)
|
|
||||||
);
|
|
||||||
|
|
||||||
const createLoggerWithLabel = async (level: string, label: string) => {
|
|
||||||
const _level = level.toLowerCase() || "info"
|
|
||||||
//* Always add Console output to transports
|
|
||||||
const _transports: any[] = [
|
|
||||||
new transports.Console({
|
|
||||||
format: combine(
|
|
||||||
colorize(),
|
|
||||||
logFormat(label),
|
|
||||||
// format.json()
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
//* Add LokiTransport if it's enabled
|
|
||||||
if((await getLokiHost()) !== undefined){
|
|
||||||
_transports.push(
|
|
||||||
new LokiTransport({
|
|
||||||
host: await getLokiHost(),
|
|
||||||
handleExceptions: true,
|
|
||||||
handleRejections: true,
|
|
||||||
batching: true,
|
|
||||||
level: _level,
|
|
||||||
timeout: 30000,
|
|
||||||
format: format.combine(
|
|
||||||
format.json()
|
|
||||||
),
|
|
||||||
labels: {
|
|
||||||
app: process.env.npm_package_name,
|
|
||||||
version: process.env.npm_package_version,
|
|
||||||
environment: await getNodeEnv(),
|
|
||||||
},
|
|
||||||
onConnectionError: (err: Error)=> console.error("Connection error while connecting to Loki Server.\n", err),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return createLogger({
|
|
||||||
level: _level,
|
|
||||||
transports: _transports,
|
|
||||||
format: format.combine(
|
|
||||||
logFormat(label),
|
|
||||||
format.metadata({ fillExcept: ["message", "level", "timestamp", "label"] })
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getLogger = async (loggerName: "backend-main" | "database") => {
|
|
||||||
const logger = {
|
|
||||||
"backend-main": await createLoggerWithLabel("info", "[IFSC:backend-main]"),
|
|
||||||
"database": await createLoggerWithLabel("info", "[IFSC:database]"),
|
|
||||||
}
|
|
||||||
return logger[loggerName]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { logger } from "./logger";
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import pino from "pino";
|
||||||
|
|
||||||
|
export const logger = pino({
|
||||||
|
level: process.env.PINO_LOG_LEVEL || "trace",
|
||||||
|
timestamp: pino.stdTimeFunctions.isoTime,
|
||||||
|
formatters: {
|
||||||
|
bindings: (bindings) => {
|
||||||
|
return {
|
||||||
|
pid: bindings.pid,
|
||||||
|
hostname: bindings.hostname
|
||||||
|
// node_version: process.version
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -2,34 +2,30 @@ import { Request } from "express"
|
|||||||
import { getVerboseErrorOutput } from "../config";
|
import { getVerboseErrorOutput } from "../config";
|
||||||
|
|
||||||
export enum LogLevel {
|
export enum LogLevel {
|
||||||
DEBUG = 100,
|
TRACE = 10,
|
||||||
INFO = 200,
|
DEBUG = 20,
|
||||||
NOTICE = 250,
|
INFO = 30,
|
||||||
WARNING = 300,
|
WARN = 40,
|
||||||
ERROR = 400,
|
ERROR = 50,
|
||||||
CRITICAL = 500,
|
FATAL = 60
|
||||||
ALERT = 550,
|
|
||||||
EMERGENCY = 600,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapToWinstonLogLevel = (customLogLevel: LogLevel): string => {
|
type PinoLogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
||||||
|
|
||||||
|
export const mapToPinoLogLevel = (customLogLevel: LogLevel): PinoLogLevel => {
|
||||||
switch (customLogLevel) {
|
switch (customLogLevel) {
|
||||||
|
case LogLevel.TRACE:
|
||||||
|
return "trace";
|
||||||
case LogLevel.DEBUG:
|
case LogLevel.DEBUG:
|
||||||
return "debug";
|
return "debug";
|
||||||
case LogLevel.INFO:
|
case LogLevel.INFO:
|
||||||
return "info";
|
return "info";
|
||||||
case LogLevel.NOTICE:
|
case LogLevel.WARN:
|
||||||
return "notice";
|
|
||||||
case LogLevel.WARNING:
|
|
||||||
return "warn";
|
return "warn";
|
||||||
case LogLevel.ERROR:
|
case LogLevel.ERROR:
|
||||||
return "error";
|
return "error";
|
||||||
case LogLevel.CRITICAL:
|
case LogLevel.FATAL:
|
||||||
return "crit";
|
return "fatal";
|
||||||
case LogLevel.ALERT:
|
|
||||||
return "alert";
|
|
||||||
case LogLevel.EMERGENCY:
|
|
||||||
return "emerg";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +38,10 @@ export type RequestErrorContext = {
|
|||||||
stack?: string|undefined
|
stack?: string|undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RequestError extends Error{
|
export default class RequestError extends Error {
|
||||||
|
|
||||||
private _logLevel: LogLevel
|
private _logLevel: LogLevel
|
||||||
private _logName: string
|
private _logName: string;
|
||||||
statusCode: number
|
statusCode: number
|
||||||
type: string
|
type: string
|
||||||
context: Record<string, unknown>
|
context: Record<string, unknown>
|
||||||
@@ -55,9 +51,10 @@ export default class RequestError extends Error{
|
|||||||
constructor(
|
constructor(
|
||||||
{logLevel, statusCode, type, message, context, stack} : RequestErrorContext
|
{logLevel, statusCode, type, message, context, stack} : RequestErrorContext
|
||||||
){
|
){
|
||||||
|
|
||||||
super(message)
|
super(message)
|
||||||
this._logLevel = logLevel || LogLevel.INFO
|
this._logLevel = logLevel || LogLevel.INFO
|
||||||
this._logName = LogLevel[this._logLevel]
|
this._logName = LogLevel[this._logLevel];
|
||||||
this.statusCode = statusCode
|
this.statusCode = statusCode
|
||||||
this.type = type
|
this.type = type
|
||||||
this.context = context || {}
|
this.context = context || {}
|
||||||
@@ -83,8 +80,12 @@ export default class RequestError extends Error{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get level(){ return this._logLevel }
|
get level(){
|
||||||
get levelName(){ return this._logName }
|
return this._logLevel
|
||||||
|
}
|
||||||
|
get levelName(){
|
||||||
|
return this._logName
|
||||||
|
}
|
||||||
|
|
||||||
withTags(...tags: string[]|number[]){
|
withTags(...tags: string[]|number[]){
|
||||||
this.context["tags"] = Object.assign(tags, this.context["tags"])
|
this.context["tags"] = Object.assign(tags, this.context["tags"])
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable no-console */
|
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
|
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
|
||||||
@@ -47,6 +46,7 @@ import {
|
|||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
memberProjectPermissions
|
memberProjectPermissions
|
||||||
} from "../../ee/services/ProjectRoleService";
|
} from "../../ee/services/ProjectRoleService";
|
||||||
|
import { logger } from "../logging";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backfill secrets to ensure that they're all versioned and have
|
* Backfill secrets to ensure that they're all versioned and have
|
||||||
@@ -88,7 +88,7 @@ export const backfillSecretVersions = async () => {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("Migration: Secret version migration v1 complete");
|
logger.info("Migration: Secret version migration v1 complete");
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -518,7 +518,7 @@ export const backfillSecretFolders = async () => {
|
|||||||
.limit(50);
|
.limit(50);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Migration: Folder migration v1 complete");
|
logger.info("Migration: Folder migration v1 complete");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const backfillServiceToken = async () => {
|
export const backfillServiceToken = async () => {
|
||||||
@@ -534,7 +534,7 @@ export const backfillServiceToken = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
console.log("Migration: Service token migration v1 complete");
|
logger.info("Migration: Service token migration v1 complete");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const backfillIntegration = async () => {
|
export const backfillIntegration = async () => {
|
||||||
@@ -550,7 +550,7 @@ export const backfillIntegration = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
console.log("Migration: Integration migration v1 complete");
|
logger.info("Migration: Integration migration v1 complete");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const backfillServiceTokenMultiScope = async () => {
|
export const backfillServiceTokenMultiScope = async () => {
|
||||||
@@ -575,7 +575,7 @@ export const backfillServiceTokenMultiScope = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Migration: Service token migration v2 complete");
|
logger.info("Migration: Service token migration v2 complete");
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -650,7 +650,7 @@ export const backfillTrustedIps = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await TrustedIP.bulkWrite(operations);
|
await TrustedIP.bulkWrite(operations);
|
||||||
console.log("Backfill: Trusted IPs complete");
|
logger.info("Backfill: Trusted IPs complete");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -698,7 +698,7 @@ export const backfillPermission = async () => {
|
|||||||
|
|
||||||
if (lock) {
|
if (lock) {
|
||||||
try {
|
try {
|
||||||
console.info("Lock acquired for script [backfillPermission]");
|
logger.info("Lock acquired for script [backfillPermission]");
|
||||||
|
|
||||||
const memberships = await Membership.find({
|
const memberships = await Membership.find({
|
||||||
deniedPermissions: {
|
deniedPermissions: {
|
||||||
@@ -801,7 +801,7 @@ export const backfillPermission = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info("Backfill: Finished converting old denied permission in workspace to viewers");
|
logger.info("Backfill: Finished converting old denied permission in workspace to viewers");
|
||||||
|
|
||||||
await MembershipOrg.updateMany(
|
await MembershipOrg.updateMany(
|
||||||
{
|
{
|
||||||
@@ -814,14 +814,14 @@ export const backfillPermission = async () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
console.info("Backfill: Finished converting owner role to member");
|
logger.info("Backfill: Finished converting owner role to member");
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("An error occurred when running script [backfillPermission]:", error);
|
logger.error(error, "An error occurred when running script [backfillPermission]");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.info("Could not acquire lock for script [backfillPermission], skipping");
|
logger.info("Could not acquire lock for script [backfillPermission], skipping");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -837,5 +837,5 @@ export const migrateRoleFromOwnerToAdmin = async () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
console.info("Backfill: Finished converting owner role to member");
|
logger.info("Backfill: Finished converting owner role to member");
|
||||||
}
|
}
|
||||||
@@ -153,29 +153,18 @@ Other environment variables are listed below to increase the functionality of yo
|
|||||||
JWT token lifetime expressed in seconds or a string describing a time span
|
JWT token lifetime expressed in seconds or a string describing a time span
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
{" "}
|
#### Logging
|
||||||
|
|
||||||
<ParamField
|
|
||||||
query="MONGO_USERNAME"
|
|
||||||
type="string"
|
|
||||||
default="none"
|
|
||||||
optional
|
|
||||||
></ParamField>
|
|
||||||
|
|
||||||
{" "}
|
|
||||||
|
|
||||||
<ParamField
|
|
||||||
query="MONGO_PASSWORD"
|
|
||||||
type="string"
|
|
||||||
default="none"
|
|
||||||
optional
|
|
||||||
></ParamField>
|
|
||||||
|
|
||||||
#### Error logging
|
|
||||||
|
|
||||||
Infisical uses Sentry to report error logs
|
Infisical uses Sentry to report error logs
|
||||||
|
|
||||||
{" "}
|
<ParamField
|
||||||
|
query="PINO_LOG_LEVEL"
|
||||||
|
type="string"
|
||||||
|
default="info"
|
||||||
|
optional
|
||||||
|
>
|
||||||
|
The minimum log level for application logging; can be one of `trace`, `debug`, `info`, `warn`, `error`, or `fatal`.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
<ParamField
|
<ParamField
|
||||||
query="SENTRY_DSN"
|
query="SENTRY_DSN"
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ By default, the application will use the `latest` docker image tag. This is okay
|
|||||||
backend:
|
backend:
|
||||||
replicaCount: 2
|
replicaCount: 2
|
||||||
image:
|
image:
|
||||||
repository: infisical/infisical
|
tag: "v0.39.5" # <--- update to the newest version found here https://hub.docker.com/r/infisical/infisical/tags
|
||||||
tag: "v0.39.5"
|
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -96,7 +95,6 @@ Managed database connection string can be set in the `backendEnvironmentVariable
|
|||||||
backend:
|
backend:
|
||||||
replicaCount: 2
|
replicaCount: 2
|
||||||
image:
|
image:
|
||||||
repository: infisical/infisical
|
|
||||||
tag: "v0.39.5"
|
tag: "v0.39.5"
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
@@ -122,7 +120,6 @@ ingress:
|
|||||||
deploymentAnnotations: {}
|
deploymentAnnotations: {}
|
||||||
replicaCount: 4
|
replicaCount: 4
|
||||||
image:
|
image:
|
||||||
repository: infisical/infisical
|
|
||||||
tag: "v0.39.5"
|
tag: "v0.39.5"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
kubeSecretRef: null
|
kubeSecretRef: null
|
||||||
|
|||||||
Generated
+7
-7
@@ -93,7 +93,7 @@
|
|||||||
"uuidv4": "^6.2.13",
|
"uuidv4": "^6.2.13",
|
||||||
"yaml": "^2.2.2",
|
"yaml": "^2.2.2",
|
||||||
"yup": "^0.32.11",
|
"yup": "^0.32.11",
|
||||||
"zod": "^3.22.0",
|
"zod": "^3.22.3",
|
||||||
"zustand": "^4.4.1"
|
"zustand": "^4.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -24758,9 +24758,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.22.0",
|
"version": "3.22.3",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
|
||||||
"integrity": "sha512-y5KZY/ssf5n7hCGDGGtcJO/EBJEm5Pa+QQvFBeyMOtnFYOSflalxIFFvdaYevPhePcmcKC4aTbFkCcXN7D0O8Q==",
|
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -42809,9 +42809,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zod": {
|
"zod": {
|
||||||
"version": "3.22.0",
|
"version": "3.22.3",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
|
||||||
"integrity": "sha512-y5KZY/ssf5n7hCGDGGtcJO/EBJEm5Pa+QQvFBeyMOtnFYOSflalxIFFvdaYevPhePcmcKC4aTbFkCcXN7D0O8Q=="
|
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="
|
||||||
},
|
},
|
||||||
"zustand": {
|
"zustand": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
"uuidv4": "^6.2.13",
|
"uuidv4": "^6.2.13",
|
||||||
"yaml": "^2.2.2",
|
"yaml": "^2.2.2",
|
||||||
"yup": "^0.32.11",
|
"yup": "^0.32.11",
|
||||||
"zod": "^3.22.0",
|
"zod": "^3.22.3",
|
||||||
"zustand": "^4.4.1"
|
"zustand": "^4.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export default function NavHeader({
|
|||||||
{isFolderMode &&
|
{isFolderMode &&
|
||||||
secretPathSegments?.map((folderName, index) => {
|
secretPathSegments?.map((folderName, index) => {
|
||||||
const query = { ...router.query };
|
const query = { ...router.query };
|
||||||
query.secretPath = secretPathSegments.slice(0, index + 1);
|
query.secretPath = `/${secretPathSegments.slice(0, index + 1).join("/")}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const ContentLoader = ({ text, frequency = 2000 }: Props) => {
|
|||||||
const [pos, setPos] = useState(0);
|
const [pos, setPos] = useState(0);
|
||||||
const isTextArray = Array.isArray(text);
|
const isTextArray = Array.isArray(text);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let interval: NodeJS.Timer;
|
let interval: NodeJS.Timeout;
|
||||||
if (isTextArray) {
|
if (isTextArray) {
|
||||||
interval = setInterval(() => {
|
interval = setInterval(() => {
|
||||||
setPos((state) => (state + 1) % text.length);
|
setPos((state) => (state + 1) % text.length);
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BillingDetails,
|
BillingDetails,
|
||||||
Invoice,
|
Invoice,
|
||||||
License,
|
License,
|
||||||
Organization,
|
Organization,
|
||||||
OrgPlanTable,
|
OrgPlanTable,
|
||||||
PlanBillingInfo,
|
PlanBillingInfo,
|
||||||
PmtMethod,
|
PmtMethod,
|
||||||
@@ -19,7 +19,8 @@ const organizationKeys = {
|
|||||||
getUserOrganizations: ["organization"] as const,
|
getUserOrganizations: ["organization"] as const,
|
||||||
getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] as const,
|
getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] as const,
|
||||||
getOrgPlanTable: (orgId: string) => [{ orgId }, "organization-plan-table"] as const,
|
getOrgPlanTable: (orgId: string) => [{ orgId }, "organization-plan-table"] as const,
|
||||||
getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") => [{ orgId, billingCycle }, "organization-plans-table"] as const,
|
getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") =>
|
||||||
|
[{ orgId, billingCycle }, "organization-plans-table"] as const,
|
||||||
getOrgBillingDetails: (orgId: string) => [{ orgId }, "organization-billing-details"] as const,
|
getOrgBillingDetails: (orgId: string) => [{ orgId }, "organization-billing-details"] as const,
|
||||||
getOrgPmtMethods: (orgId: string) => [{ orgId }, "organization-pmt-methods"] as const,
|
getOrgPmtMethods: (orgId: string) => [{ orgId }, "organization-pmt-methods"] as const,
|
||||||
getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const,
|
getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const,
|
||||||
@@ -28,34 +29,36 @@ const organizationKeys = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const fetchOrganizations = async () => {
|
export const fetchOrganizations = async () => {
|
||||||
const { data: { organizations } } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization");
|
const {
|
||||||
|
data: { organizations }
|
||||||
|
} = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization");
|
||||||
return organizations;
|
return organizations;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useGetOrganizations = () => {
|
export const useGetOrganizations = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: organizationKeys.getUserOrganizations,
|
queryKey: organizationKeys.getUserOrganizations,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return fetchOrganizations();
|
return fetchOrganizations();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useCreateOrg = () => {
|
export const useCreateOrg = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({ name }: { name: string }) => {
|
||||||
name
|
const {
|
||||||
}: {
|
data: { organization }
|
||||||
name: string;
|
} = await apiRequest.post("/api/v2/organizations", {
|
||||||
}) => {
|
name
|
||||||
const { data: { organization } } = await apiRequest.post(
|
});
|
||||||
"/api/v2/organizations",
|
|
||||||
{
|
|
||||||
name
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return organization;
|
return organization;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -75,17 +78,13 @@ export const useRenameOrg = () => {
|
|||||||
|
|
||||||
export const useGetOrgTrialUrl = () => {
|
export const useGetOrgTrialUrl = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({ orgId, success_url }: { orgId: string; success_url: string }) => {
|
||||||
orgId,
|
const {
|
||||||
success_url
|
data: { url }
|
||||||
}: {
|
} = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, {
|
||||||
orgId: string;
|
|
||||||
success_url: string;
|
|
||||||
}) => {
|
|
||||||
const { data: { url } } = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, {
|
|
||||||
success_url
|
success_url
|
||||||
})
|
});
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -99,11 +98,11 @@ export const useGetOrgPlanBillingInfo = (organizationId: string) => {
|
|||||||
`/api/v1/organizations/${organizationId}/plan/billing`
|
`/api/v1/organizations/${organizationId}/plan/billing`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useGetOrgPlanTable = (organizationId: string) => {
|
export const useGetOrgPlanTable = (organizationId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -113,18 +112,18 @@ export const useGetOrgPlanTable = (organizationId: string) => {
|
|||||||
`/api/v1/organizations/${organizationId}/plan/table`
|
`/api/v1/organizations/${organizationId}/plan/table`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useGetOrgPlansTable = ({
|
export const useGetOrgPlansTable = ({
|
||||||
organizationId,
|
organizationId,
|
||||||
billingCycle
|
billingCycle
|
||||||
}: {
|
}: {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
billingCycle: "monthly" | "yearly"
|
billingCycle: "monthly" | "yearly";
|
||||||
}) => {
|
}) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: organizationKeys.getOrgPlansTable(organizationId, billingCycle),
|
queryKey: organizationKeys.getOrgPlansTable(organizationId, billingCycle),
|
||||||
@@ -133,11 +132,11 @@ export const useGetOrgPlansTable = ({
|
|||||||
`/api/v1/organizations/${organizationId}/plans/table?billingCycle=${billingCycle}`
|
`/api/v1/organizations/${organizationId}/plans/table?billingCycle=${billingCycle}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useGetOrgBillingDetails = (organizationId: string) => {
|
export const useGetOrgBillingDetails = (organizationId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -151,7 +150,7 @@ export const useGetOrgBillingDetails = (organizationId: string) => {
|
|||||||
},
|
},
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useUpdateOrgBillingDetails = () => {
|
export const useUpdateOrgBillingDetails = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -166,7 +165,7 @@ export const useUpdateOrgBillingDetails = () => {
|
|||||||
email?: string;
|
email?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.patch(
|
const { data } = await apiRequest.patch(
|
||||||
`/api/v1/organizations/${organizationId}/billing-details`,
|
`/api/v1/organizations/${organizationId}/billing-details`,
|
||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
email
|
email
|
||||||
@@ -193,7 +192,7 @@ export const useGetOrgPmtMethods = (organizationId: string) => {
|
|||||||
},
|
},
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useAddOrgPmtMethod = () => {
|
export const useAddOrgPmtMethod = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -208,8 +207,10 @@ export const useAddOrgPmtMethod = () => {
|
|||||||
success_url: string;
|
success_url: string;
|
||||||
cancel_url: string;
|
cancel_url: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { data: { url } } = await apiRequest.post(
|
const {
|
||||||
`/api/v1/organizations/${organizationId}/billing-details/payment-methods`,
|
data: { url }
|
||||||
|
} = await apiRequest.post(
|
||||||
|
`/api/v1/organizations/${organizationId}/billing-details/payment-methods`,
|
||||||
{
|
{
|
||||||
success_url,
|
success_url,
|
||||||
cancel_url
|
cancel_url
|
||||||
@@ -230,7 +231,7 @@ export const useDeleteOrgPmtMethod = () => {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
organizationId,
|
organizationId,
|
||||||
pmtMethodId,
|
pmtMethodId
|
||||||
}: {
|
}: {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
pmtMethodId: string;
|
pmtMethodId: string;
|
||||||
@@ -245,7 +246,7 @@ export const useDeleteOrgPmtMethod = () => {
|
|||||||
queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId));
|
queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useGetOrgTaxIds = (organizationId: string) => {
|
export const useGetOrgTaxIds = (organizationId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -259,7 +260,7 @@ export const useGetOrgTaxIds = (organizationId: string) => {
|
|||||||
},
|
},
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useAddOrgTaxId = () => {
|
export const useAddOrgTaxId = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -275,7 +276,7 @@ export const useAddOrgTaxId = () => {
|
|||||||
value: string;
|
value: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.post(
|
const { data } = await apiRequest.post(
|
||||||
`/api/v1/organizations/${organizationId}/billing-details/tax-ids`,
|
`/api/v1/organizations/${organizationId}/billing-details/tax-ids`,
|
||||||
{
|
{
|
||||||
type,
|
type,
|
||||||
value
|
value
|
||||||
@@ -294,13 +295,7 @@ export const useDeleteOrgTaxId = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({ organizationId, taxId }: { organizationId: string; taxId: string }) => {
|
||||||
organizationId,
|
|
||||||
taxId,
|
|
||||||
}: {
|
|
||||||
organizationId: string;
|
|
||||||
taxId: string;
|
|
||||||
}) => {
|
|
||||||
const { data } = await apiRequest.delete(
|
const { data } = await apiRequest.delete(
|
||||||
`/api/v1/organizations/${organizationId}/billing-details/tax-ids/${taxId}`
|
`/api/v1/organizations/${organizationId}/billing-details/tax-ids/${taxId}`
|
||||||
);
|
);
|
||||||
@@ -311,7 +306,7 @@ export const useDeleteOrgTaxId = () => {
|
|||||||
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
|
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useGetOrgInvoices = (organizationId: string) => {
|
export const useGetOrgInvoices = (organizationId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -325,7 +320,7 @@ export const useGetOrgInvoices = (organizationId: string) => {
|
|||||||
},
|
},
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useCreateCustomerPortalSession = () => {
|
export const useCreateCustomerPortalSession = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@@ -343,7 +338,7 @@ export const useGetOrgLicenses = (organizationId: string) => {
|
|||||||
queryKey: organizationKeys.getOrgLicenses(organizationId),
|
queryKey: organizationKeys.getOrgLicenses(organizationId),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (organizationId === "") return undefined;
|
if (organizationId === "") return undefined;
|
||||||
|
|
||||||
const { data } = await apiRequest.get<License[]>(
|
const { data } = await apiRequest.get<License[]>(
|
||||||
`/api/v1/organizations/${organizationId}/licenses`
|
`/api/v1/organizations/${organizationId}/licenses`
|
||||||
);
|
);
|
||||||
@@ -352,18 +347,16 @@ export const useGetOrgLicenses = (organizationId: string) => {
|
|||||||
},
|
},
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const useDeleteOrgById = () => {
|
export const useDeleteOrgById = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({ organizationId }: { organizationId: string }) => {
|
||||||
organizationId,
|
const {
|
||||||
}: {
|
data: { organization }
|
||||||
organizationId: string;
|
} = await apiRequest.delete<{ organization: Organization }>(
|
||||||
}) => {
|
|
||||||
const { data: { organization } } = await apiRequest.delete<{ organization: Organization }>(
|
|
||||||
`/api/v2/organizations/${organizationId}`
|
`/api/v2/organizations/${organizationId}`
|
||||||
);
|
);
|
||||||
return organization;
|
return organization;
|
||||||
@@ -372,8 +365,12 @@ export const useDeleteOrgById = () => {
|
|||||||
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
|
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
|
||||||
queryClient.invalidateQueries(organizationKeys.getOrgPlanBillingInfo(dto.organizationId));
|
queryClient.invalidateQueries(organizationKeys.getOrgPlanBillingInfo(dto.organizationId));
|
||||||
queryClient.invalidateQueries(organizationKeys.getOrgPlanTable(dto.organizationId));
|
queryClient.invalidateQueries(organizationKeys.getOrgPlanTable(dto.organizationId));
|
||||||
queryClient.invalidateQueries(organizationKeys.getOrgPlansTable(dto.organizationId, "monthly")); // You might need to invalidate for 'yearly' as well.
|
queryClient.invalidateQueries(
|
||||||
queryClient.invalidateQueries(organizationKeys.getOrgPlansTable(dto.organizationId, "yearly"));
|
organizationKeys.getOrgPlansTable(dto.organizationId, "monthly")
|
||||||
|
); // You might need to invalidate for 'yearly' as well.
|
||||||
|
queryClient.invalidateQueries(
|
||||||
|
organizationKeys.getOrgPlansTable(dto.organizationId, "yearly")
|
||||||
|
);
|
||||||
queryClient.invalidateQueries(organizationKeys.getOrgBillingDetails(dto.organizationId));
|
queryClient.invalidateQueries(organizationKeys.getOrgBillingDetails(dto.organizationId));
|
||||||
queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId));
|
queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId));
|
||||||
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
|
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
|
||||||
@@ -381,4 +378,4 @@ export const useDeleteOrgById = () => {
|
|||||||
queryClient.invalidateQueries(organizationKeys.getOrgLicenses(dto.organizationId));
|
queryClient.invalidateQueries(organizationKeys.getOrgLicenses(dto.organizationId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ import {
|
|||||||
useGetUserAction,
|
useGetUserAction,
|
||||||
useLogoutUser,
|
useLogoutUser,
|
||||||
useRegisterUserAction,
|
useRegisterUserAction,
|
||||||
useUploadWsKey} from "@app/hooks/api";
|
useUploadWsKey
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { CreateOrgModal } from "@app/views/Org/components";
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -114,12 +116,12 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
const { workspaces, currentWorkspace } = useWorkspace();
|
const { workspaces, currentWorkspace } = useWorkspace();
|
||||||
const { orgs, currentOrg } = useOrganization();
|
const { orgs, currentOrg } = useOrganization();
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const workspaceId = currentWorkspace?._id || "";
|
const workspaceId = currentWorkspace?._id || "";
|
||||||
const { data: updateClosed } = useGetUserAction("september_update_closed");
|
const { data: updateClosed } = useGetUserAction("september_update_closed");
|
||||||
|
|
||||||
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId });
|
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId });
|
||||||
|
|
||||||
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
||||||
@@ -133,7 +135,8 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"addNewWs",
|
"addNewWs",
|
||||||
"upgradePlan"
|
"upgradePlan",
|
||||||
|
"createOrg"
|
||||||
] as const);
|
] as const);
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -150,7 +153,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
|
|
||||||
const closeUpdate = async () => {
|
const closeUpdate = async () => {
|
||||||
await registerUserAction.mutateAsync("september_update_closed");
|
await registerUserAction.mutateAsync("september_update_closed");
|
||||||
}
|
};
|
||||||
|
|
||||||
const logout = useLogoutUser();
|
const logout = useLogoutUser();
|
||||||
const logOutUser = async () => {
|
const logOutUser = async () => {
|
||||||
@@ -316,6 +319,22 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
|
<DropdownMenuItem key="add-org">
|
||||||
|
<Button
|
||||||
|
onClick={() => handlePopUpOpen("createOrg")}
|
||||||
|
variant="plain"
|
||||||
|
colorSchema="secondary"
|
||||||
|
size="xs"
|
||||||
|
className="flex w-full items-center justify-start p-0 font-normal"
|
||||||
|
leftIcon={
|
||||||
|
<FontAwesomeIcon icon={faPlus} className="mr-3 text-primary" />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex w-full items-center justify-between">
|
||||||
|
Create New Organization
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuItem>
|
||||||
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||||
<button type="button" onClick={logOutUser} className="w-full">
|
<button type="button" onClick={logOutUser} className="w-full">
|
||||||
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||||
@@ -488,7 +507,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
>
|
>
|
||||||
Secret approvals
|
Secret approvals
|
||||||
{Boolean(secretApprovalReqCount?.open) && (
|
{Boolean(secretApprovalReqCount?.open) && (
|
||||||
<span className="text-xs font-semibold py-0.5 px-1 rounded ml-2 bg-primary-600 border border-primary-400 text-black">
|
<span className="ml-2 rounded border border-primary-400 bg-primary-600 py-0.5 px-1 text-xs font-semibold text-black">
|
||||||
{secretApprovalReqCount?.open}
|
{secretApprovalReqCount?.open}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -599,17 +618,31 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[10.7rem] ${router.asPath.includes("org") ? "bottom-[8.15rem]" : "bottom-[5.15rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-50`}/>
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[10.7rem] ${router.asPath.includes("org") ? "bottom-[8.15rem]" : "bottom-[5.15rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-50`}/>
|
||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[11.5rem] ${router.asPath.includes("org") ? "bottom-[7.9rem]" : "bottom-[4.9rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-70`}/>
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[11.5rem] ${router.asPath.includes("org") ? "bottom-[7.9rem]" : "bottom-[4.9rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-70`}/>
|
||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[12.3rem] ${router.asPath.includes("org") ? "bottom-[7.65rem]" : "bottom-[4.65rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-90`}/> */}
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[12.3rem] ${router.asPath.includes("org") ? "bottom-[7.65rem]" : "bottom-[4.65rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-90`}/> */}
|
||||||
<div className={`${!updateClosed ? "block" : "hidden"} relative z-10 h-64 w-52 bg-mineshaft-900 border border-mineshaft-600 mb-6 rounded-md flex flex-col items-center justify-start px-3`}>
|
<div
|
||||||
<div className="w-full mt-2 text-md text-mineshaft-100 font-semibold">Infisical September update</div>
|
className={`${
|
||||||
<div className="w-full mt-1 text-sm text-mineshaft-300 font-normal leading-[1.2rem] mb-1">Improved RBAC, new integrations, dashboard remake, and more!</div>
|
!updateClosed ? "block" : "hidden"
|
||||||
<div className="h-[6.77rem] w-full rounded-md mt-2 border border-mineshaft-700">
|
} relative z-10 mb-6 flex h-64 w-52 flex-col items-center justify-start rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3`}
|
||||||
<Image src="/images/infisical-update-september-2023.png" height={319} width={539} alt="kubernetes image" className="rounded-sm" />
|
>
|
||||||
|
<div className="text-md mt-2 w-full font-semibold text-mineshaft-100">
|
||||||
|
Infisical September update
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex justify-between items-center mt-3 px-0.5">
|
<div className="mt-1 mb-1 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300">
|
||||||
|
Improved RBAC, new integrations, dashboard remake, and more!
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 h-[6.77rem] w-full rounded-md border border-mineshaft-700">
|
||||||
|
<Image
|
||||||
|
src="/images/infisical-update-september-2023.png"
|
||||||
|
height={319}
|
||||||
|
width={539}
|
||||||
|
alt="kubernetes image"
|
||||||
|
className="rounded-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 flex w-full items-center justify-between px-0.5">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => closeUpdate()}
|
onClick={() => closeUpdate()}
|
||||||
className="text-mineshaft-400 hover:text-mineshaft-100 duration-200"
|
className="text-mineshaft-400 duration-200 hover:text-mineshaft-100"
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
@@ -617,9 +650,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
href="https://infisical.com/blog/infisical-update-september-2023"
|
href="https://infisical.com/blog/infisical-update-september-2023"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-sm text-mineshaft-400 font-normal leading-[1.2rem] hover:text-mineshaft-100 duration-200"
|
className="text-sm font-normal leading-[1.2rem] text-mineshaft-400 duration-200 hover:text-mineshaft-100"
|
||||||
>
|
>
|
||||||
Learn More <FontAwesomeIcon icon={faArrowUpRightFromSquare} className="text-xs pl-0.5"/>
|
Learn More{" "}
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="pl-0.5 text-xs" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -778,6 +812,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text="You have exceeded the number of projects allowed on the free plan."
|
text="You have exceeded the number of projects allowed on the free plan."
|
||||||
/>
|
/>
|
||||||
|
<CreateOrgModal
|
||||||
|
isOpen={popUp?.createOrg?.isOpen}
|
||||||
|
onClose={() => handlePopUpToggle("createOrg", false)}
|
||||||
|
/>
|
||||||
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,114 +1,16 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { Controller, useForm } from "react-hook-form";
|
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
|
||||||
import * as yup from "yup";
|
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
FormControl,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalContent} from "@app/components/v2";
|
|
||||||
import { useCreateOrg } from "@app/hooks/api";
|
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const schema = yup.object({
|
import { CreateOrgModal } from "../components";
|
||||||
name: yup.string().required("Organization name is required"),
|
|
||||||
}).required();
|
|
||||||
|
|
||||||
export type FormData = yup.InferType<typeof schema>;
|
|
||||||
|
|
||||||
export const NonePage = () => {
|
export const NonePage = () => {
|
||||||
const { createNotification } = useNotificationContext();
|
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
|
||||||
"createOrg",
|
|
||||||
] as const);
|
|
||||||
|
|
||||||
const { mutateAsync } = useCreateOrg();
|
|
||||||
|
|
||||||
const {
|
|
||||||
control,
|
|
||||||
handleSubmit,
|
|
||||||
reset,
|
|
||||||
formState: { isSubmitting }
|
|
||||||
} = useForm<FormData>({
|
|
||||||
resolver: yupResolver(schema),
|
|
||||||
defaultValues: {
|
|
||||||
name: ""
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
handlePopUpOpen("createOrg");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onFormSubmit = async ({ name }: FormData) => {
|
|
||||||
try {
|
|
||||||
|
|
||||||
const organization = await mutateAsync({
|
|
||||||
name
|
|
||||||
});
|
|
||||||
|
|
||||||
localStorage.setItem("orgData.id", organization._id);
|
|
||||||
|
|
||||||
createNotification({
|
return (
|
||||||
text: "Successfully created organization",
|
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||||
type: "success"
|
<CreateOrgModal
|
||||||
});
|
isOpen={popUp.createOrg.isOpen}
|
||||||
|
onClose={() => handlePopUpToggle("createOrg", false)}
|
||||||
window.location.href = `/org/${organization._id}/overview`;
|
/>
|
||||||
|
</div>
|
||||||
reset();
|
);
|
||||||
handlePopUpToggle("createOrg", false);
|
};
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
text: "Failed to created organization",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
|
||||||
<Modal
|
|
||||||
isOpen={popUp?.createOrg?.isOpen}
|
|
||||||
>
|
|
||||||
<ModalContent
|
|
||||||
title="Create Organization"
|
|
||||||
subTitle="Looks like you're not part of any organizations. Create one to start using Infisical"
|
|
||||||
>
|
|
||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
defaultValue=""
|
|
||||||
name="name"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Name"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder="Acme Corp"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className=""
|
|
||||||
size="sm"
|
|
||||||
type="submit"
|
|
||||||
isLoading={isSubmitting}
|
|
||||||
isDisabled={isSubmitting}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
|
||||||
|
import { useCreateOrg } from "@app/hooks/api";
|
||||||
|
|
||||||
|
const schema = z
|
||||||
|
.object({
|
||||||
|
name: z.string().nonempty({ message: "Name is required" })
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
interface CreateOrgModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) => {
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
name: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutateAsync } = useCreateOrg();
|
||||||
|
|
||||||
|
const onFormSubmit = async ({ name }: FormData) => {
|
||||||
|
try {
|
||||||
|
const organization = await mutateAsync({
|
||||||
|
name
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully created organization",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (router.isReady) router.push(`/org/${organization._id}/overview`);
|
||||||
|
else window.location.href = `/org/${organization._id}/overview`;
|
||||||
|
|
||||||
|
localStorage.setItem("orgData.id", organization._id);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
onClose();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to created organization",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen}>
|
||||||
|
<ModalContent
|
||||||
|
title="Create Organization"
|
||||||
|
subTitle="Looks like you're not part of any organizations. Create one to start using Infisical"
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="name"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl label="Name" isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Input {...field} placeholder="Acme Corp" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex w-full gap-4">
|
||||||
|
<Button
|
||||||
|
className=""
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
<Button className="" size="sm" variant="outline_bg" type="button" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { CreateOrgModal } from "./CreateOrgModal";
|
||||||
+1
-1
@@ -164,7 +164,7 @@ export const SecretApprovalPolicyList = ({ workspaceId }: Props) => {
|
|||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text="You can add secret approval policy if you switch to Infisical's Team plan."
|
text="You can add secret approval policy if you switch to Infisical's Enterprise plan."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -172,7 +172,11 @@ export const SecretItem = memo(
|
|||||||
|
|
||||||
const copyTokenToClipboard = () => {
|
const copyTokenToClipboard = () => {
|
||||||
const [overrideValue, value] = getValues(["value", "valueOverride"]);
|
const [overrideValue, value] = getValues(["value", "valueOverride"]);
|
||||||
navigator.clipboard.writeText((overrideValue || value) as string);
|
if (isOverriden) {
|
||||||
|
navigator.clipboard.writeText(value as string);
|
||||||
|
} else {
|
||||||
|
navigator.clipboard.writeText(overrideValue as string);
|
||||||
|
}
|
||||||
setIsSecValueCopied.on();
|
setIsSecValueCopied.on();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -111,14 +111,15 @@ export const SecretOverviewPage = () => {
|
|||||||
try {
|
try {
|
||||||
// create folder if not existing
|
// create folder if not existing
|
||||||
if (secretPath !== "/") {
|
if (secretPath !== "/") {
|
||||||
const path = secretPath.split("/");
|
// /hello/world -> [hello","world"]
|
||||||
const directory = path.slice(0, -1).join("/");
|
const pathSegment = secretPath.split("/").filter(Boolean);
|
||||||
const folderName = path.at(-1);
|
const parentPath = `/${pathSegment.slice(0, -1).join("/")}`;
|
||||||
if (folderName && directory) {
|
const folderName = pathSegment.at(-1);
|
||||||
|
if (folderName && parentPath) {
|
||||||
await createFolder({
|
await createFolder({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment: env,
|
environment: env,
|
||||||
directory,
|
directory: parentPath,
|
||||||
folderName
|
folderName
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 0.4.1
|
version: 0.4.2
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ spec:
|
|||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: {{ $backend.kubeSecretRef | default (include "infisical.backend.fullname" .) }}
|
name: {{ $backend.kubeSecretRef | default (include "infisical.backend.fullname" .) }}
|
||||||
# {{- if $backend.resources }}
|
{{- if $backend.resources }}
|
||||||
# resources: {{- toYaml $backend.resources | nindent 12 }}
|
resources: {{- toYaml $backend.resources | nindent 12 }}
|
||||||
# {{- end }}
|
{{- end }}
|
||||||
---
|
---
|
||||||
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
|||||||
Reference in New Issue
Block a user