Merge remote-tracking branch 'origin' into checkly-sync-on-group-level

This commit is contained in:
Tuan Dang
2023-11-01 20:37:15 +02:00
38 changed files with 2014 additions and 2404 deletions
+1177 -1370
View File
File diff suppressed because it is too large Load Diff
+12 -6
View File
@@ -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();
+16 -17
View File
@@ -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);
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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;
} }
+86 -163
View File
@@ -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
View File
@@ -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();
+27 -31
View File
@@ -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();
}; };
+1 -3
View File
@@ -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);
+4 -4
View File
@@ -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));
} }
} }
+25 -9
View File
@@ -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;
}; };
+2 -2
View File
@@ -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) {
+3 -4
View File
@@ -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;
+1 -1
View File
@@ -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",
-67
View File
@@ -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]
}
+1
View File
@@ -0,0 +1 @@
export { logger } from "./logger";
+15
View File
@@ -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
};
},
}
});
+24 -23
View File
@@ -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"])
+13 -13
View File
@@ -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");
} }
+9 -20
View File
@@ -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
+7 -7
View File
@@ -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",
+1 -1
View File
@@ -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);
+62 -65
View File
@@ -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));
} }
}); });
} };
+53 -15
View File
@@ -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>
+11 -109
View File
@@ -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";
@@ -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
}); });
} }
+1 -1
View File
@@ -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