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

pull/1145/head
Tuan Dang 7 months ago
commit f256493cb3

File diff suppressed because it is too large Load Diff

@ -6,7 +6,7 @@
"@godaddy/terminus": "^4.12.0",
"@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.49.0",
"@sentry/node": "^7.77.0",
"@sentry/tracing": "^7.48.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",
@ -19,7 +19,7 @@
"bigint-conversion": "^2.4.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"crypto-js": "^4.2.0",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"express-async-errors": "^3.1.1",
@ -34,7 +34,6 @@
"jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21",
"mongodb": "^5.7.0",
"mongoose": "^7.4.1",
"nanoid": "^3.3.6",
"node-cache": "^5.1.2",
@ -43,6 +42,8 @@
"passport-github": "^1.1.0",
"passport-gitlab2": "^5.0.0",
"passport-google-oauth20": "^2.0.0",
"pino": "^8.16.1",
"pino-http": "^8.5.1",
"posthog-node": "^2.6.0",
"probot": "^12.3.1",
"query-string": "^7.1.3",
@ -53,16 +54,19 @@
"tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3",
"utility-types": "^3.10.0",
"winston": "^3.8.2",
"winston-loki": "^6.0.6",
"zod": "^3.22.3"
},
"overrides": {
"rate-limit-mongo": {
"mongodb": "5.8.0"
}
},
"name": "infisical-api",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "node build/index.js",
"dev": "nodemon",
"dev": "nodemon index.js | pino-pretty --colorize",
"swagger-autogen": "node ./swagger/index.ts",
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build",
"lint": "eslint . --ext .ts",
@ -100,6 +104,7 @@
"@types/nodemailer": "^6.4.6",
"@types/passport": "^1.0.12",
"@types/picomatch": "^2.3.0",
"@types/pino": "^7.0.5",
"@types/supertest": "^2.0.12",
"@types/swagger-jsdoc": "^6.0.1",
"@types/swagger-ui-express": "^4.1.3",
@ -113,6 +118,7 @@
"jest-junit": "^15.0.0",
"nodemon": "^2.0.19",
"npm": "^8.19.3",
"pino-pretty": "^10.2.3",
"smee-client": "^1.2.3",
"supertest": "^6.3.3",
"swagger-autogen": "^2.23.5",

@ -28,7 +28,6 @@ import {
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { getIntegrationAuthAccessHelper } from "../../helpers";
import { ObjectId } from "mongodb";
/***
* 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
const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
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
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(

@ -1,12 +1,16 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { client, getRootEncryptionKey } from "../../config";
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
import { Webhook } from "../../models";
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services";
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 * as reqValidator from "../../validation/webhooks";
import {
@ -15,6 +19,7 @@ import {
getUserProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
export const createWebhook = async (req: Request, res: Response) => {
const {
@ -31,17 +36,31 @@ export const createWebhook = async (req: Request, res: Response) => {
workspace: workspaceId,
environment,
secretPath,
url: webhookUrl,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_BASE64
url: webhookUrl
});
if (webhookSecretKey) {
const encryptionKey = await getEncryptionKey();
const rootEncryptionKey = await getRootEncryptionKey();
const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey);
webhook.iv = iv;
webhook.tag = tag;
webhook.encryptedSecretKey = ciphertext;
if (rootEncryptionKey) {
const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey);
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();

@ -11,7 +11,6 @@ import {
ValidationError as RouteValidationError,
UnauthorizedRequestError
} from "../../utils/errors";
import { AnyBulkWriteOperation } from "mongodb";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
@ -19,7 +18,7 @@ import {
SECRET_SHARED
} from "../../variables";
import { TelemetryService } from "../../services";
import { ISecret, Secret, User } from "../../models";
import { Secret, User } from "../../models";
import { AccountNotFoundError } from "../../utils/errors";
/**
@ -145,22 +144,22 @@ export const deleteSecrets = async (req: Request, res: Response) => {
const secretsUserCanDeleteSet: Set<string> = new Set(
secretIdsUserCanDelete.map((objectId) => objectId._id.toString())
);
const deleteOperationsToPerform: AnyBulkWriteOperation<ISecret>[] = [];
let numSecretsDeleted = 0;
secretIdsToDelete.forEach((secretIdToDelete) => {
if (secretsUserCanDeleteSet.has(secretIdToDelete)) {
const deleteOperation = {
deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } }
};
deleteOperationsToPerform.push(deleteOperation);
numSecretsDeleted++;
} else {
throw RouteValidationError({
message: "You cannot delete secrets that you do not have access to"
});
}
});
// Filter out IDs that user can delete and then map them to delete operations
const deleteOperationsToPerform = secretIdsToDelete
.filter(secretIdToDelete => {
if (!secretsUserCanDeleteSet.has(secretIdToDelete)) {
throw RouteValidationError({
message: "You cannot delete secrets that you do not have access to"
});
}
return true;
})
.map(secretIdToDelete => ({
deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } }
}));
const numSecretsDeleted = deleteOperationsToPerform.length;
await Secret.bulkWrite(deleteOperationsToPerform);

@ -1,5 +1,5 @@
import mongoose from "mongoose";
import { getLogger } from "../utils/logger";
import { logger } from "../utils/logging";
/**
* Initialize database connection
@ -18,10 +18,10 @@ export const initDatabaseHelper = async ({
// allow empty strings to pass the required validator
mongoose.Schema.Types.String.checkRequired(v => typeof v === "string");
(await getLogger("database")).info("Database connection established");
logger.info("Database connection established");
} 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;

@ -1,4 +1,4 @@
import mongoose, { Types, mongo } from "mongoose";
import { Types } from "mongoose";
import {
Bot,
BotKey,
@ -55,7 +55,7 @@ import {
import {
createBotOrg
} from "./botOrg";
import { InternalServerError, ResourceNotFoundError } from "../utils/errors";
import { ResourceNotFoundError } from "../utils/errors";
/**
* Create an organization with name [name]
@ -111,311 +111,215 @@ export const createOrganization = async ({
* @returns
*/
export const deleteOrganization = async ({
organizationId,
existingSession
organizationId
}: {
organizationId: Types.ObjectId;
existingSession?: mongo.ClientSession;
}) => {
let session;
if (existingSession) {
session = existingSession;
} else {
session = await mongoose.startSession();
session.startTransaction();
}
const organization = await Organization.findByIdAndDelete(
organizationId
);
try {
const organization = await Organization.findByIdAndDelete(
organizationId,
{
session
}
);
if (!organization) throw ResourceNotFoundError();
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 MembershipOrg.deleteMany({
organization: organization._id
});
await BotOrg.deleteMany({
organization: organization._id
});
await SSOConfig.deleteMany({
organization: organization._id
});
await Role.deleteMany({
organization: organization._id
});
await Key.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Bot.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await BotKey.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await IncidentContactOrg.deleteMany({
organization: organization._id
});
await GitRisks.deleteMany({
organization: organization._id
});
await GitAppInstallationSession.deleteMany({
organization: organization._id
});
await GitAppOrganizationInstallation.deleteMany({
organization: organization._id
});
await SecretBlindIndexData.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Secret.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await SecretVersion.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
const workspaceIds = await Workspace.distinct("_id", {
organization: organization._id
});
await Workspace.deleteMany({
organization: organization._id
});
await Membership.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretSnapshot.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await SecretImport.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Key.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Bot.deleteMany({
workspace: {
$in: workspaceIds
}
});
await BotKey.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Folder.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await SecretBlindIndexData.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Secret.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretVersion.deleteMany({
workspace: {
$in: workspaceIds
}
});
await FolderVersion.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await SecretSnapshot.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretImport.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Webhook.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Folder.deleteMany({
workspace: {
$in: workspaceIds
}
});
await TrustedIP.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Tag.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await FolderVersion.deleteMany({
workspace: {
$in: workspaceIds
}
});
await IntegrationAuth.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Webhook.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Integration.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await TrustedIP.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Tag.deleteMany({
workspace: {
$in: workspaceIds
}
});
await ServiceToken.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await IntegrationAuth.deleteMany({
workspace: {
$in: workspaceIds
}
});
await ServiceTokenData.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Integration.deleteMany({
workspace: {
$in: workspaceIds
}
});
await ServiceTokenDataV3.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await ServiceTokenDataV3Key.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await ServiceToken.deleteMany({
workspace: {
$in: workspaceIds
}
});
await AuditLog.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await ServiceTokenData.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Log.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await ServiceTokenDataV3.deleteMany({
workspace: {
$in: workspaceIds
}
});
await ServiceTokenDataV3Key.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Action.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await AuditLog.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretApprovalPolicy.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Log.deleteMany({
workspace: {
$in: workspaceIds
}
});
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}`
);
await Action.deleteMany({
workspace: {
$in: workspaceIds
}
return organization;
} catch (err) {
if (!existingSession) {
await session.abortTransaction();
});
await SecretApprovalPolicy.deleteMany({
workspace: {
$in: workspaceIds
}
throw InternalServerError({
message: "Failed to delete organization"
});
} finally {
if (!existingSession) {
await session.commitTransaction();
session.endSession();
});
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}`
);
}
return organization;
}
/**

@ -1,4 +1,4 @@
import mongoose, { Types, mongo } from "mongoose";
import { Types } from "mongoose";
import {
APIKeyData,
BackupPrivateKey,
@ -222,141 +222,92 @@ const checkDeleteUserConditions = async ({
* @returns {User} user - deleted user
*/
export const deleteUser = async ({
userId,
existingSession
userId
}: {
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) {
session = existingSession;
} else {
session = await mongoose.startSession();
session.startTransaction();
}
await UserAction.deleteMany({
user: user._id
});
try {
const user = await User.findByIdAndDelete(userId, {
session
});
if (!user) throw ResourceNotFoundError();
await BackupPrivateKey.deleteMany({
user: user._id
});
await checkDeleteUserConditions({
userId: user._id
});
await UserAction.deleteMany({
user: user._id
}, {
session
});
await APIKeyData.deleteMany({
user: user._id
});
await BackupPrivateKey.deleteMany({
user: user._id
}, {
session
});
await Action.deleteMany({
user: user._id
});
await Log.deleteMany({
user: user._id
});
await APIKeyData.deleteMany({
user: user._id
}, {
session
});
await TokenVersion.deleteMany({
user: user._id
});
await Action.deleteMany({
user: user._id
}, {
session
});
await Log.deleteMany({
user: user._id
}, {
session
});
await Key.deleteMany({
receiver: user._id
});
await TokenVersion.deleteMany({
user: user._id
});
const membershipOrgs = await MembershipOrg.find({
user: userId
});
await Key.deleteMany({
receiver: user._id
}, {
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)
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
await deleteOrganization({
organizationId: 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
const memberships = await Membership.find({
user: userId
});
// delete workspaces where user is only member
for await (const membership of memberships) {
const memberCount = await Membership.countDocuments({
workspace: membership.workspace
});
await Membership.deleteMany({
user: userId
}, {
session
});
if (memberCount === 1) {
// workspace only has 1 member (the current user) -> delete workspace
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 deleteWorkspace({
workspaceId: membership.workspace
});
}
}
await MembershipOrg.deleteMany({
user: userId
});
await Membership.deleteMany({
user: userId
});
return user;
}

@ -1,4 +1,4 @@
import mongoose, { Types, mongo } from "mongoose";
import { Types } from "mongoose";
import {
Bot,
BotKey,
@ -33,8 +33,7 @@ import {
import { createBot } from "../helpers/bot";
import { EELicenseService } from "../ee/services";
import { SecretService } from "../services";
import {
InternalServerError,
import {
ResourceNotFoundError
} from "../utils/errors";
@ -102,189 +101,113 @@ export const createWorkspace = async ({
* @param {String} obj.id - id of workspace to delete
*/
export const deleteWorkspace = async ({
workspaceId,
existingSession
workspaceId
}: {
workspaceId: Types.ObjectId;
existingSession?: mongo.ClientSession;
}) => {
let session;
if (existingSession) {
session = existingSession;
} else {
session = await mongoose.startSession();
session.startTransaction();
}
const workspace = await Workspace.findByIdAndDelete(workspaceId);
try {
const workspace = await Workspace.findByIdAndDelete(workspaceId, { session });
if (!workspace) throw ResourceNotFoundError();
await Membership.deleteMany({
workspace: workspace._id
}, {
session
});
await Key.deleteMany({
workspace: workspace._id
}, {
session
});
await Bot.deleteMany({
workspace: workspace._id
}, {
session
});
if (!workspace) throw ResourceNotFoundError();
await Membership.deleteMany({
workspace: workspace._id
});
await Key.deleteMany({
workspace: workspace._id
});
await Bot.deleteMany({
workspace: workspace._id
});
await BotKey.deleteMany({
workspace: workspace._id
}, {
session
});
await BotKey.deleteMany({
workspace: workspace._id
});
await SecretBlindIndexData.deleteMany({
workspace: workspace._id
}, {
session
});
await SecretBlindIndexData.deleteMany({
workspace: workspace._id
});
await Secret.deleteMany({
workspace: workspace._id
}, {
session
});
await SecretVersion.deleteMany({
workspace: workspace._id
}, {
session
});
await Secret.deleteMany({
workspace: workspace._id
});
await SecretVersion.deleteMany({
workspace: workspace._id
});
await SecretSnapshot.deleteMany({
workspace: workspace._id
}, {
session
});
await SecretSnapshot.deleteMany({
workspace: workspace._id
});
await SecretImport.deleteMany({
workspace: workspace._id
}, {
session
});
await SecretImport.deleteMany({
workspace: workspace._id
});
await Folder.deleteMany({
workspace: workspace._id
}, {
session
});
await Folder.deleteMany({
workspace: workspace._id
});
await FolderVersion.deleteMany({
workspace: workspace._id
}, {
session
});
await FolderVersion.deleteMany({
workspace: workspace._id
});
await Webhook.deleteMany({
workspace: workspace._id
}, {
session
});
await Webhook.deleteMany({
workspace: workspace._id
});
await TrustedIP.deleteMany({
workspace: workspace._id
}, {
session
});
await TrustedIP.deleteMany({
workspace: workspace._id
});
await Tag.deleteMany({
workspace: workspace._id
}, {
session
});
await Tag.deleteMany({
workspace: workspace._id
});
await IntegrationAuth.deleteMany({
workspace: workspace._id
}, {
session
});
await IntegrationAuth.deleteMany({
workspace: workspace._id
});
await Integration.deleteMany({
workspace: workspace._id
}, {
session
});
await Integration.deleteMany({
workspace: workspace._id
});
await ServiceToken.deleteMany({
workspace: workspace._id
}, {
session
});
await ServiceToken.deleteMany({
workspace: workspace._id
});
await ServiceTokenData.deleteMany({
workspace: workspace._id
}, {
session
});
await ServiceTokenData.deleteMany({
workspace: workspace._id
});
await ServiceTokenDataV3.deleteMany({
workspace: workspace._id
}, {
session
});
await ServiceTokenDataV3.deleteMany({
workspace: workspace._id
});
await ServiceTokenDataV3Key.deleteMany({
workspace: workspace._id
}, {
session
});
await ServiceTokenDataV3Key.deleteMany({
workspace: workspace._id
});
await AuditLog.deleteMany({
workspace: workspace._id
}, {
session
});
await AuditLog.deleteMany({
workspace: workspace._id
});
await Log.deleteMany({
workspace: workspace._id
}, {
session
});
await Log.deleteMany({
workspace: workspace._id
});
await Action.deleteMany({
workspace: workspace._id
}, {
session
});
await Action.deleteMany({
workspace: workspace._id
});
await SecretApprovalPolicy.deleteMany({
workspace: workspace._id
}, {
session
});
await SecretApprovalPolicy.deleteMany({
workspace: workspace._id
});
await SecretApprovalRequest.deleteMany({
workspace: workspace._id
}, {
session
});
return workspace;
} catch (err) {
if (!existingSession) {
await session.abortTransaction();
}
throw InternalServerError({
message: "Failed to delete organization"
});
} finally {
if (!existingSession) {
await session.commitTransaction();
session.endSession();
}
}
await SecretApprovalRequest.deleteMany({
workspace: workspace._id
});
return workspace;
};

@ -5,6 +5,8 @@ import express from "express";
require("express-async-errors");
import helmet from "helmet";
import cors from "cors";
import { logger } from "./utils/logging";
import httpLogger from "pino-http";
import { DatabaseService } from "./services";
import { EELicenseService, GithubSecretScanningService } from "./ee/services";
import { setUpHealthEndpoint } from "./services/health";
@ -73,7 +75,7 @@ import {
workspaces as v3WorkspacesRouter
} from "./routes/v3";
import { healthCheck } from "./routes/status";
import { getLogger } from "./utils/logger";
// import { getLogger } from "./utils/logger";
import { RouteNotFoundError } from "./utils/errors";
import { requestErrorHandler } from "./middleware/requestErrorHandler";
import {
@ -94,12 +96,20 @@ import path from "path";
let handler: null | any = null;
const main = async () => {
const port = await getPort();
await setup();
await EELicenseService.initGlobalFeatureSet();
const app = express();
app.enable("trust proxy");
app.use(httpLogger({
logger,
autoLogging: false
}));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
@ -164,7 +174,7 @@ const main = async () => {
const nextApp = new NextServer({
dev: false,
dir: nextJsBuildPath,
port: await getPort(),
port,
conf,
hostname: "local",
customServer: false
@ -255,8 +265,8 @@ const main = async () => {
app.use(requestErrorHandler);
const server = app.listen(await getPort(), async () => {
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`);
const server = app.listen(port, async () => {
logger.info(`Server started listening at port ${port}`);
});
// await createTestUserForDevelopment();

@ -2,49 +2,45 @@ import * as Sentry from "@sentry/node";
import { ErrorRequestHandler } from "express";
import { TokenExpiredError } from "jsonwebtoken";
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
import { getLogger } from "../utils/logger";
import RequestError from "../utils/requestError";
import { logger } from "../utils/logging";
import RequestError, { mapToPinoLogLevel } from "../utils/requestError";
import { ForbiddenError } from "@casl/ability";
export const requestErrorHandler: ErrorRequestHandler = async (
error: RequestError | Error,
err: RequestError | Error,
req,
res,
next
) => {
if (res.headersSent) return next();
const logAndCaptureException = async (error: RequestError) => {
(await getLogger("backend-main")).log(
(<RequestError>error).levelName.toLowerCase(),
`${error.stack}\n${error.message}`
);
//* Set Sentry user identification if req.user is populated
if (req.user !== undefined && req.user !== null) {
Sentry.setUser({ email: (req.user as any).email });
}
Sentry.captureException(error);
};
let error: RequestError;
switch (true) {
case err instanceof TokenExpiredError:
error = UnauthorizedRequestError({ stack: err.stack, message: "Token expired" });
break;
case err instanceof ForbiddenError:
error = UnauthorizedRequestError({ context: { exception: err.message }, stack: err.stack })
break;
case err instanceof RequestError:
error = err as RequestError;
break;
default:
error = InternalServerError({ context: { exception: err.message }, stack: err.stack });
break;
}
if (error instanceof RequestError) {
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 });
}
logger[mapToPinoLogLevel(error.level)](error);
await logAndCaptureException((<RequestError>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
res.status((<RequestError>error).statusCode).json(error);
res.status((<RequestError>error).statusCode).json(error); // revise json part here
next();
};

@ -65,13 +65,11 @@ const WebhookSchema = new Schema<IWebhook>(
// the encryption algorithm used
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true,
select: false
},
keyEncoding: {
type: String,
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
required: true,
select: false
}
},
@ -80,4 +78,4 @@ const WebhookSchema = new Schema<IWebhook>(
}
);
export const Webhook = model<IWebhook>("Webhook", WebhookSchema);
export const Webhook = model<IWebhook>("Webhook", WebhookSchema);

@ -1,5 +1,5 @@
import { PostHog } from "posthog-node";
import { getLogger } from "../utils/logger";
import { logger } from "../utils/logging";
import { AuthData } from "../interfaces/middleware";
import {
getNodeEnv,
@ -22,13 +22,13 @@ class Telemetry {
* Logs telemetry enable/disable notice.
*/
static logTelemetryMessage = async () => {
if(!(await getTelemetryEnabled())){
(await getLogger("backend-main")).info([
"",
[
"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.",
"To opt into telemetry, you can set `TELEMETRY_ENABLED=true` within the environment variables.",
].join("\n"))
].forEach(line => logger.info(line));
}
}

@ -2,26 +2,42 @@ import axios from "axios";
import crypto from "crypto";
import { Types } from "mongoose";
import picomatch from "picomatch";
import { client, getRootEncryptionKey } from "../config";
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
import { IWebhook, Webhook } from "../models";
import { decryptSymmetric128BitHexKeyUTF8 } from "../utils/crypto";
import { ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8 } from "../variables";
export const triggerWebhookRequest = async (
{ url, encryptedSecretKey, iv, tag }: IWebhook,
{ url, encryptedSecretKey, iv, tag, keyEncoding }: IWebhook,
payload: Record<string, unknown>
) => {
const headers: Record<string, string> = {};
payload["timestamp"] = Date.now();
if (encryptedSecretKey) {
const encryptionKey = await getEncryptionKey();
const rootEncryptionKey = await getRootEncryptionKey();
const secretKey = client.decryptSymmetric(encryptedSecretKey, rootEncryptionKey, iv, tag);
const webhookSign = crypto
.createHmac("sha256", secretKey)
.update(JSON.stringify(payload))
.digest("hex");
headers["x-infisical-signature"] = `t=${payload["timestamp"]};${webhookSign}`;
let secretKey;
if (rootEncryptionKey && keyEncoding === ENCODING_SCHEME_BASE64) {
// case: encoding scheme is base64
secretKey = client.decryptSymmetric(encryptedSecretKey, rootEncryptionKey, iv, tag);
} else if (encryptionKey && keyEncoding === ENCODING_SCHEME_UTF8) {
// 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 });
return req;
};

@ -1,10 +1,10 @@
import mongoose from "mongoose";
import { createTerminus } from "@godaddy/terminus";
import { getLogger } from "../utils/logger";
import { logger } from "../utils/logging";
export const setUpHealthEndpoint = <T>(server: T) => {
const onSignal = async () => {
(await getLogger("backend-main")).info("Server is starting clean-up");
logger.info("Server is starting clean-up");
return Promise.all([
new Promise((resolve) => {
if (mongoose.connection && mongoose.connection.readyState == 1) {

@ -16,7 +16,7 @@ import {
getSmtpSecure,
getSmtpUsername,
} from "../config";
import { getLogger } from "../utils/logger";
import { logger } from "../utils/logging";
export const initSmtp = async () => {
const mailOpts: SMTPConnection.Options = {
@ -84,15 +84,14 @@ export const initSmtp = async () => {
.then(async () => {
Sentry.setUser(null);
Sentry.captureMessage("SMTP - Successfully connected");
(await getLogger("backend-main")).info(
"SMTP - Successfully connected"
);
logger.info("SMTP - Successfully connected");
})
.catch(async (err) => {
Sentry.setUser(null);
Sentry.captureException(
`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;

@ -29,7 +29,7 @@ export const UnauthorizedRequestError = (error?: Partial<RequestErrorContext>) =
});
export const ForbiddenRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO,
logLevel: error?.logLevel ?? LogLevel.WARN,
statusCode: error?.statusCode ?? 403,
type: error?.type ?? "forbidden",
message: error?.message ?? "You are not allowed to access this resource",

@ -1,67 +0,0 @@
/* eslint-disable no-console */
import { createLogger, format, transports } from "winston";
import LokiTransport from "winston-loki";
import { getLokiHost, getNodeEnv } from "../config";
const { combine, colorize, label, printf, splat, timestamp } = format;
const logFormat = (prefix: string) => combine(
timestamp(),
splat(),
label({ label: prefix }),
printf((info) => `${info.timestamp} ${info.label} ${info.level}: ${info.message}`)
);
const createLoggerWithLabel = async (level: string, label: string) => {
const _level = level.toLowerCase() || "info"
//* Always add Console output to transports
const _transports: any[] = [
new transports.Console({
format: combine(
colorize(),
logFormat(label),
// format.json()
),
}),
]
//* Add LokiTransport if it's enabled
if((await getLokiHost()) !== undefined){
_transports.push(
new LokiTransport({
host: await getLokiHost(),
handleExceptions: true,
handleRejections: true,
batching: true,
level: _level,
timeout: 30000,
format: format.combine(
format.json()
),
labels: {
app: process.env.npm_package_name,
version: process.env.npm_package_version,
environment: await getNodeEnv(),
},
onConnectionError: (err: Error)=> console.error("Connection error while connecting to Loki Server.\n", err),
})
)
}
return createLogger({
level: _level,
transports: _transports,
format: format.combine(
logFormat(label),
format.metadata({ fillExcept: ["message", "level", "timestamp", "label"] })
),
});
}
export const getLogger = async (loggerName: "backend-main" | "database") => {
const logger = {
"backend-main": await createLoggerWithLabel("info", "[IFSC:backend-main]"),
"database": await createLoggerWithLabel("info", "[IFSC:database]"),
}
return logger[loggerName]
}

@ -0,0 +1 @@
export { logger } from "./logger";

@ -0,0 +1,15 @@
import pino from "pino";
export const logger = pino({
level: process.env.PINO_LOG_LEVEL || "trace",
timestamp: pino.stdTimeFunctions.isoTime,
formatters: {
bindings: (bindings) => {
return {
pid: bindings.pid,
hostname: bindings.hostname
// node_version: process.version
};
},
}
});

@ -2,34 +2,30 @@ import { Request } from "express"
import { getVerboseErrorOutput } from "../config";
export enum LogLevel {
DEBUG = 100,
INFO = 200,
NOTICE = 250,
WARNING = 300,
ERROR = 400,
CRITICAL = 500,
ALERT = 550,
EMERGENCY = 600,
TRACE = 10,
DEBUG = 20,
INFO = 30,
WARN = 40,
ERROR = 50,
FATAL = 60
}
export const mapToWinstonLogLevel = (customLogLevel: LogLevel): string => {
type PinoLogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
export const mapToPinoLogLevel = (customLogLevel: LogLevel): PinoLogLevel => {
switch (customLogLevel) {
case LogLevel.TRACE:
return "trace";
case LogLevel.DEBUG:
return "debug";
case LogLevel.INFO:
return "info";
case LogLevel.NOTICE:
return "notice";
case LogLevel.WARNING:
case LogLevel.WARN:
return "warn";
case LogLevel.ERROR:
return "error";
case LogLevel.CRITICAL:
return "crit";
case LogLevel.ALERT:
return "alert";
case LogLevel.EMERGENCY:
return "emerg";
case LogLevel.FATAL:
return "fatal";
}
}
@ -42,10 +38,10 @@ export type RequestErrorContext = {
stack?: string|undefined
}
export default class RequestError extends Error{
export default class RequestError extends Error {
private _logLevel: LogLevel
private _logName: string
private _logName: string;
statusCode: number
type: string
context: Record<string, unknown>
@ -55,9 +51,10 @@ export default class RequestError extends Error{
constructor(
{logLevel, statusCode, type, message, context, stack} : RequestErrorContext
){
super(message)
this._logLevel = logLevel || LogLevel.INFO
this._logName = LogLevel[this._logLevel]
this._logName = LogLevel[this._logLevel];
this.statusCode = statusCode
this.type = type
this.context = context || {}
@ -83,8 +80,12 @@ export default class RequestError extends Error{
})
}
get level(){ return this._logLevel }
get levelName(){ return this._logName }
get level(){
return this._logLevel
}
get levelName(){
return this._logName
}
withTags(...tags: string[]|number[]){
this.context["tags"] = Object.assign(tags, this.context["tags"])

@ -1,4 +1,3 @@
/* eslint-disable no-console */
import crypto from "crypto";
import { Types } from "mongoose";
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
@ -47,6 +46,7 @@ import {
ProjectPermissionSub,
memberProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { logger } from "../logging";
/**
* 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);
}
console.log("Migration: Folder migration v1 complete");
logger.info("Migration: Folder migration v1 complete");
};
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 () => {
@ -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 () => {
@ -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);
console.log("Backfill: Trusted IPs complete");
logger.info("Backfill: Trusted IPs complete");
}
};
@ -698,7 +698,7 @@ export const backfillPermission = async () => {
if (lock) {
try {
console.info("Lock acquired for script [backfillPermission]");
logger.info("Lock acquired for script [backfillPermission]");
const memberships = await Membership.find({
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(
{
@ -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) {
console.error("An error occurred when running script [backfillPermission]:", error);
logger.error(error, "An error occurred when running script [backfillPermission]");
}
} else {
console.info("Could not acquire lock for script [backfillPermission], skipping");
logger.info("Could not acquire lock for script [backfillPermission], skipping");
}
};
@ -837,5 +837,5 @@ export const migrateRoleFromOwnerToAdmin = async () => {
}
);
console.info("Backfill: Finished converting owner role to member");
logger.info("Backfill: Finished converting owner role to member");
}

@ -153,29 +153,18 @@ Other environment variables are listed below to increase the functionality of yo
JWT token lifetime expressed in seconds or a string describing a time span
</ParamField>
{" "}
#### Logging
<ParamField
query="MONGO_USERNAME"
type="string"
default="none"
optional
></ParamField>
{" "}
Infisical uses Sentry to report error logs
<ParamField
query="MONGO_PASSWORD"
query="PINO_LOG_LEVEL"
type="string"
default="none"
default="info"
optional
></ParamField>
#### Error logging
Infisical uses Sentry to report error logs
{" "}
>
The minimum log level for application logging; can be one of `trace`, `debug`, `info`, `warn`, `error`, or `fatal`.
</ParamField>
<ParamField
query="SENTRY_DSN"

@ -36,8 +36,7 @@ By default, the application will use the `latest` docker image tag. This is okay
backend:
replicaCount: 2
image:
repository: infisical/infisical
tag: "v0.39.5"
tag: "v0.39.5" # <--- update to the newest version found here https://hub.docker.com/r/infisical/infisical/tags
pullPolicy: Always
```
@ -96,7 +95,6 @@ Managed database connection string can be set in the `backendEnvironmentVariable
backend:
replicaCount: 2
image:
repository: infisical/infisical
tag: "v0.39.5"
pullPolicy: Always
@ -122,7 +120,6 @@ ingress:
deploymentAnnotations: {}
replicaCount: 4
image:
repository: infisical/infisical
tag: "v0.39.5"
pullPolicy: IfNotPresent
kubeSecretRef: null

@ -93,7 +93,7 @@
"uuidv4": "^6.2.13",
"yaml": "^2.2.2",
"yup": "^0.32.11",
"zod": "^3.22.0",
"zod": "^3.22.3",
"zustand": "^4.4.1"
},
"devDependencies": {
@ -24758,9 +24758,9 @@
}
},
"node_modules/zod": {
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.0.tgz",
"integrity": "sha512-y5KZY/ssf5n7hCGDGGtcJO/EBJEm5Pa+QQvFBeyMOtnFYOSflalxIFFvdaYevPhePcmcKC4aTbFkCcXN7D0O8Q==",
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@ -42809,9 +42809,9 @@
}
},
"zod": {
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.0.tgz",
"integrity": "sha512-y5KZY/ssf5n7hCGDGGtcJO/EBJEm5Pa+QQvFBeyMOtnFYOSflalxIFFvdaYevPhePcmcKC4aTbFkCcXN7D0O8Q=="
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="
},
"zustand": {
"version": "4.4.1",

@ -101,7 +101,7 @@
"uuidv4": "^6.2.13",
"yaml": "^2.2.2",
"yup": "^0.32.11",
"zod": "^3.22.0",
"zod": "^3.22.3",
"zustand": "^4.4.1"
},
"devDependencies": {

@ -131,7 +131,7 @@ export default function NavHeader({
{isFolderMode &&
secretPathSegments?.map((folderName, index) => {
const query = { ...router.query };
query.secretPath = secretPathSegments.slice(0, index + 1);
query.secretPath = `/${secretPathSegments.slice(0, index + 1).join("/")}`;
return (
<div

@ -13,7 +13,7 @@ export const ContentLoader = ({ text, frequency = 2000 }: Props) => {
const [pos, setPos] = useState(0);
const isTextArray = Array.isArray(text);
useEffect(() => {
let interval: NodeJS.Timer;
let interval: NodeJS.Timeout;
if (isTextArray) {
interval = setInterval(() => {
setPos((state) => (state + 1) % text.length);

@ -2,11 +2,11 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import {
import {
BillingDetails,
Invoice,
License,
Organization,
Organization,
OrgPlanTable,
PlanBillingInfo,
PmtMethod,
@ -19,7 +19,8 @@ const organizationKeys = {
getUserOrganizations: ["organization"] as const,
getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] 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,
getOrgPmtMethods: (orgId: string) => [{ orgId }, "organization-pmt-methods"] as const,
getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const,
@ -28,34 +29,36 @@ const organizationKeys = {
};
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;
}
};
export const useGetOrganizations = () => {
return useQuery({
queryKey: organizationKeys.getUserOrganizations,
return useQuery({
queryKey: organizationKeys.getUserOrganizations,
queryFn: async () => {
return fetchOrganizations();
}
});
}
};
export const useCreateOrg = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
name
}: {
name: string;
}) => {
const { data: { organization } } = await apiRequest.post(
"/api/v2/organizations",
{
name
}
);
mutationFn: async ({ name }: { name: string }) => {
const {
data: { organization }
} = await apiRequest.post("/api/v2/organizations", {
name
});
return organization;
},
onSuccess: () => {
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
}
});
};
@ -75,17 +78,13 @@ export const useRenameOrg = () => {
export const useGetOrgTrialUrl = () => {
return useMutation({
mutationFn: async ({
orgId,
success_url
}: {
orgId: string;
success_url: string;
}) => {
const { data: { url } } = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, {
mutationFn: async ({ orgId, success_url }: { orgId: string; success_url: string }) => {
const {
data: { url }
} = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, {
success_url
})
});
return url;
}
});
@ -99,11 +98,11 @@ export const useGetOrgPlanBillingInfo = (organizationId: string) => {
`/api/v1/organizations/${organizationId}/plan/billing`
);
return data;
return data;
},
enabled: true
});
}
};
export const useGetOrgPlanTable = (organizationId: string) => {
return useQuery({
@ -113,18 +112,18 @@ export const useGetOrgPlanTable = (organizationId: string) => {
`/api/v1/organizations/${organizationId}/plan/table`
);
return data;
return data;
},
enabled: true
});
}
};
export const useGetOrgPlansTable = ({
organizationId,
billingCycle
}: {
organizationId: string;
billingCycle: "monthly" | "yearly"
billingCycle: "monthly" | "yearly";
}) => {
return useQuery({
queryKey: organizationKeys.getOrgPlansTable(organizationId, billingCycle),
@ -133,11 +132,11 @@ export const useGetOrgPlansTable = ({
`/api/v1/organizations/${organizationId}/plans/table?billingCycle=${billingCycle}`
);
return data;
return data;
},
enabled: true
});
}
};
export const useGetOrgBillingDetails = (organizationId: string) => {
return useQuery({
@ -151,7 +150,7 @@ export const useGetOrgBillingDetails = (organizationId: string) => {
},
enabled: true
});
}
};
export const useUpdateOrgBillingDetails = () => {
const queryClient = useQueryClient();
@ -166,7 +165,7 @@ export const useUpdateOrgBillingDetails = () => {
email?: string;
}) => {
const { data } = await apiRequest.patch(
`/api/v1/organizations/${organizationId}/billing-details`,
`/api/v1/organizations/${organizationId}/billing-details`,
{
name,
email
@ -193,7 +192,7 @@ export const useGetOrgPmtMethods = (organizationId: string) => {
},
enabled: true
});
}
};
export const useAddOrgPmtMethod = () => {
const queryClient = useQueryClient();
@ -208,8 +207,10 @@ export const useAddOrgPmtMethod = () => {
success_url: string;
cancel_url: string;
}) => {
const { data: { url } } = await apiRequest.post(
`/api/v1/organizations/${organizationId}/billing-details/payment-methods`,
const {
data: { url }
} = await apiRequest.post(
`/api/v1/organizations/${organizationId}/billing-details/payment-methods`,
{
success_url,
cancel_url
@ -230,7 +231,7 @@ export const useDeleteOrgPmtMethod = () => {
return useMutation({
mutationFn: async ({
organizationId,
pmtMethodId,
pmtMethodId
}: {
organizationId: string;
pmtMethodId: string;
@ -245,7 +246,7 @@ export const useDeleteOrgPmtMethod = () => {
queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId));
}
});
}
};
export const useGetOrgTaxIds = (organizationId: string) => {
return useQuery({
@ -259,7 +260,7 @@ export const useGetOrgTaxIds = (organizationId: string) => {
},
enabled: true
});
}
};
export const useAddOrgTaxId = () => {
const queryClient = useQueryClient();
@ -275,7 +276,7 @@ export const useAddOrgTaxId = () => {
value: string;
}) => {
const { data } = await apiRequest.post(
`/api/v1/organizations/${organizationId}/billing-details/tax-ids`,
`/api/v1/organizations/${organizationId}/billing-details/tax-ids`,
{
type,
value
@ -294,13 +295,7 @@ export const useDeleteOrgTaxId = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
organizationId,
taxId,
}: {
organizationId: string;
taxId: string;
}) => {
mutationFn: async ({ organizationId, taxId }: { organizationId: string; taxId: string }) => {
const { data } = await apiRequest.delete(
`/api/v1/organizations/${organizationId}/billing-details/tax-ids/${taxId}`
);
@ -311,7 +306,7 @@ export const useDeleteOrgTaxId = () => {
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
}
});
}
};
export const useGetOrgInvoices = (organizationId: string) => {
return useQuery({
@ -325,7 +320,7 @@ export const useGetOrgInvoices = (organizationId: string) => {
},
enabled: true
});
}
};
export const useCreateCustomerPortalSession = () => {
return useMutation({
@ -343,7 +338,7 @@ export const useGetOrgLicenses = (organizationId: string) => {
queryKey: organizationKeys.getOrgLicenses(organizationId),
queryFn: async () => {
if (organizationId === "") return undefined;
const { data } = await apiRequest.get<License[]>(
`/api/v1/organizations/${organizationId}/licenses`
);
@ -352,18 +347,16 @@ export const useGetOrgLicenses = (organizationId: string) => {
},
enabled: true
});
}
};
export const useDeleteOrgById = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
organizationId,
}: {
organizationId: string;
}) => {
const { data: { organization } } = await apiRequest.delete<{ organization: Organization }>(
mutationFn: async ({ organizationId }: { organizationId: string }) => {
const {
data: { organization }
} = await apiRequest.delete<{ organization: Organization }>(
`/api/v2/organizations/${organizationId}`
);
return organization;
@ -372,8 +365,12 @@ export const useDeleteOrgById = () => {
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
queryClient.invalidateQueries(organizationKeys.getOrgPlanBillingInfo(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(organizationKeys.getOrgPlansTable(dto.organizationId, "yearly"));
queryClient.invalidateQueries(
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.getOrgPmtMethods(dto.organizationId));
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
@ -381,4 +378,4 @@ export const useDeleteOrgById = () => {
queryClient.invalidateQueries(organizationKeys.getOrgLicenses(dto.organizationId));
}
});
}
};

@ -70,7 +70,9 @@ import {
useGetUserAction,
useLogoutUser,
useRegisterUserAction,
useUploadWsKey} from "@app/hooks/api";
useUploadWsKey
} from "@app/hooks/api";
import { CreateOrgModal } from "@app/views/Org/components";
interface LayoutProps {
children: React.ReactNode;
@ -114,12 +116,12 @@ export const AppLayout = ({ children }: LayoutProps) => {
// eslint-disable-next-line prefer-const
const { workspaces, currentWorkspace } = useWorkspace();
const { orgs, currentOrg } = useOrganization();
const { user } = useUser();
const { subscription } = useSubscription();
const workspaceId = currentWorkspace?._id || "";
const { data: updateClosed } = useGetUserAction("september_update_closed");
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId });
const isAddingProjectsAllowed = subscription?.workspaceLimit
@ -133,7 +135,8 @@ export const AppLayout = ({ children }: LayoutProps) => {
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"addNewWs",
"upgradePlan"
"upgradePlan",
"createOrg"
] as const);
const {
control,
@ -150,7 +153,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
const closeUpdate = async () => {
await registerUserAction.mutateAsync("september_update_closed");
}
};
const logout = useLogoutUser();
const logOutUser = async () => {
@ -316,6 +319,22 @@ export const AppLayout = ({ children }: LayoutProps) => {
</Button>
</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" />
<button type="button" onClick={logOutUser} className="w-full">
<DropdownMenuItem>Log Out</DropdownMenuItem>
@ -488,7 +507,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
>
Secret approvals
{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}
</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-[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={`${!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 className="w-full mt-2 text-md text-mineshaft-100 font-semibold">Infisical September update</div>
<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>
<div className="h-[6.77rem] w-full rounded-md mt-2 border border-mineshaft-700">
<Image src="/images/infisical-update-september-2023.png" height={319} width={539} alt="kubernetes image" className="rounded-sm" />
<div
className={`${
!updateClosed ? "block" : "hidden"
} 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`}
>
<div className="text-md mt-2 w-full font-semibold text-mineshaft-100">
Infisical September update
</div>
<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="w-full flex justify-between items-center mt-3 px-0.5">
<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
type="button"
onClick={() => closeUpdate()}
className="text-mineshaft-400 hover:text-mineshaft-100 duration-200"
className="text-mineshaft-400 duration-200 hover:text-mineshaft-100"
>
Close
</button>
@ -617,9 +650,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
href="https://infisical.com/blog/infisical-update-september-2023"
target="_blank"
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>
</div>
</div>
@ -778,6 +812,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
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]">
{children}
</main>

@ -1,114 +1,16 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import {
Button,
FormControl,
Input,
Modal,
ModalContent} from "@app/components/v2";
import { useCreateOrg } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
const schema = yup.object({
name: yup.string().required("Organization name is required"),
}).required();
export type FormData = yup.InferType<typeof schema>;
import { CreateOrgModal } from "../components";
export const NonePage = () => {
const { createNotification } = useNotificationContext();
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({
text: "Successfully created organization",
type: "success"
});
window.location.href = `/org/${organization._id}/overview`;
reset();
handlePopUpToggle("createOrg", false);
} catch (err) {
console.error(err);
createNotification({
text: "Failed to created organization",
type: "error"
});
}
}
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
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>
);
}
return (
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<CreateOrgModal
isOpen={popUp.createOrg.isOpen}
onClose={() => handlePopUpToggle("createOrg", false)}
/>
</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
isOpen={popUp.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>
);

@ -172,7 +172,11 @@ export const SecretItem = memo(
const copyTokenToClipboard = () => {
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();
};

@ -111,14 +111,15 @@ export const SecretOverviewPage = () => {
try {
// create folder if not existing
if (secretPath !== "/") {
const path = secretPath.split("/");
const directory = path.slice(0, -1).join("/");
const folderName = path.at(-1);
if (folderName && directory) {
// /hello/world -> [hello","world"]
const pathSegment = secretPath.split("/").filter(Boolean);
const parentPath = `/${pathSegment.slice(0, -1).join("/")}`;
const folderName = pathSegment.at(-1);
if (folderName && parentPath) {
await createFolder({
workspaceId,
environment: env,
directory,
directory: parentPath,
folderName
});
}

@ -7,7 +7,7 @@ type: application
# 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.
# 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
# incremented each time you make changes to the application. Versions are not expected to

@ -44,9 +44,9 @@ spec:
envFrom:
- secretRef:
name: {{ $backend.kubeSecretRef | default (include "infisical.backend.fullname" .) }}
# {{- if $backend.resources }}
# resources: {{- toYaml $backend.resources | nindent 12 }}
# {{- end }}
{{- if $backend.resources }}
resources: {{- toYaml $backend.resources | nindent 12 }}
{{- end }}
---
apiVersion: v1

Loading…
Cancel
Save