mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 22:28:15 +00:00
Merge remote-tracking branch 'origin' into feature-github-signin
This commit is contained in:
@@ -137,6 +137,12 @@ export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete payment method with id [pmtMethodId] for organization
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => {
|
export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => {
|
||||||
const { pmtMethodId } = req.params;
|
const { pmtMethodId } = req.params;
|
||||||
|
|
||||||
@@ -207,3 +213,17 @@ export const getOrganizationInvoices = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
return res.status(200).send(invoices);
|
return res.status(200).send(invoices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return organization's licenses on file
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getOrganizationLicenses = async (req: Request, res: Response) => {
|
||||||
|
const { data: { licenses } } = await licenseServerKeyRequest.get(
|
||||||
|
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/licenses`
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send(licenses);
|
||||||
|
}
|
||||||
@@ -220,4 +220,18 @@ router.get(
|
|||||||
organizationsController.getOrganizationInvoices
|
organizationsController.getOrganizationInvoices
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/:organizationId/licenses",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
}),
|
||||||
|
param("organizationId").exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
organizationsController.getOrganizationLicenses
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
+85
-43
@@ -4,18 +4,20 @@ import {
|
|||||||
decryptAsymmetric,
|
decryptAsymmetric,
|
||||||
decryptSymmetric128BitHexKeyUTF8,
|
decryptSymmetric128BitHexKeyUTF8,
|
||||||
encryptSymmetric128BitHexKeyUTF8,
|
encryptSymmetric128BitHexKeyUTF8,
|
||||||
generateKeyPair,
|
generateKeyPair
|
||||||
} from "../utils/crypto";
|
} from "../utils/crypto";
|
||||||
import {
|
import {
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_BASE64,
|
ENCODING_SCHEME_BASE64,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
SECRET_SHARED,
|
SECRET_SHARED
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||||
import { InternalServerError } from "../utils/errors";
|
import { InternalServerError } from "../utils/errors";
|
||||||
import Folder from "../models/folder";
|
import Folder from "../models/folder";
|
||||||
import { getFolderByPath } from "../services/FolderService";
|
import { getFolderByPath } from "../services/FolderService";
|
||||||
|
import { getAllImportedSecrets } from "../services/SecretImportService";
|
||||||
|
import { expandSecrets } from "./secrets";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
||||||
@@ -25,7 +27,7 @@ import { getFolderByPath } from "../services/FolderService";
|
|||||||
*/
|
*/
|
||||||
export const createBot = async ({
|
export const createBot = async ({
|
||||||
name,
|
name,
|
||||||
workspaceId,
|
workspaceId
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
@@ -36,10 +38,7 @@ export const createBot = async ({
|
|||||||
const { publicKey, privateKey } = generateKeyPair();
|
const { publicKey, privateKey } = generateKeyPair();
|
||||||
|
|
||||||
if (rootEncryptionKey) {
|
if (rootEncryptionKey) {
|
||||||
const { ciphertext, iv, tag } = client.encryptSymmetric(
|
const { ciphertext, iv, tag } = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
||||||
privateKey,
|
|
||||||
rootEncryptionKey
|
|
||||||
);
|
|
||||||
|
|
||||||
return await new Bot({
|
return await new Bot({
|
||||||
name,
|
name,
|
||||||
@@ -50,12 +49,12 @@ export const createBot = async ({
|
|||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_BASE64,
|
keyEncoding: ENCODING_SCHEME_BASE64
|
||||||
}).save();
|
}).save();
|
||||||
} else if (encryptionKey) {
|
} else if (encryptionKey) {
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext: privateKey,
|
plaintext: privateKey,
|
||||||
key: await getEncryptionKey(),
|
key: await getEncryptionKey()
|
||||||
});
|
});
|
||||||
|
|
||||||
return await new Bot({
|
return await new Bot({
|
||||||
@@ -67,12 +66,12 @@ export const createBot = async ({
|
|||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
}).save();
|
}).save();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw InternalServerError({
|
throw InternalServerError({
|
||||||
message: "Failed to create new bot due to missing encryption key",
|
message: "Failed to create new bot due to missing encryption key"
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ export const createBot = async ({
|
|||||||
*/
|
*/
|
||||||
export const getIsWorkspaceE2EEHelper = async (workspaceId: Types.ObjectId) => {
|
export const getIsWorkspaceE2EEHelper = async (workspaceId: Types.ObjectId) => {
|
||||||
const botKey = await BotKey.exists({
|
const botKey = await BotKey.exists({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
return botKey ? false : true;
|
return botKey ? false : true;
|
||||||
@@ -98,19 +97,19 @@ export const getIsWorkspaceE2EEHelper = async (workspaceId: Types.ObjectId) => {
|
|||||||
export const getSecretsBotHelper = async ({
|
export const getSecretsBotHelper = async ({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath
|
||||||
}: {
|
}: {
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
}) => {
|
}) => {
|
||||||
const content = {} as any;
|
const content: Record<string, { value: string; comment?: string }> = {};
|
||||||
const key = await getKey({ workspaceId: workspaceId });
|
const key = await getKey({ workspaceId: workspaceId });
|
||||||
|
|
||||||
let folderId = "root";
|
let folderId = "root";
|
||||||
const folders = await Folder.findOne({
|
const folders = await Folder.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!folders && secretPath !== "/") {
|
if (!folders && secretPath !== "/") {
|
||||||
@@ -129,7 +128,43 @@ export const getSecretsBotHelper = async ({
|
|||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
type: SECRET_SHARED,
|
type: SECRET_SHARED,
|
||||||
folder: folderId,
|
folder: folderId
|
||||||
|
});
|
||||||
|
|
||||||
|
const importedSecrets = await getAllImportedSecrets(
|
||||||
|
workspaceId.toString(),
|
||||||
|
environment,
|
||||||
|
folderId
|
||||||
|
);
|
||||||
|
|
||||||
|
importedSecrets.forEach(({ secrets }) => {
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
|
iv: secret.secretKeyIV,
|
||||||
|
tag: secret.secretKeyTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: secret.secretValueCiphertext,
|
||||||
|
iv: secret.secretValueIV,
|
||||||
|
tag: secret.secretValueTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
content[secretKey] = { value: secretValue };
|
||||||
|
|
||||||
|
if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
|
||||||
|
const commentValue = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: secret.secretCommentCiphertext,
|
||||||
|
iv: secret.secretCommentIV,
|
||||||
|
tag: secret.secretCommentTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
content[secretKey].comment = commentValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
secrets.forEach((secret: ISecret) => {
|
secrets.forEach((secret: ISecret) => {
|
||||||
@@ -137,19 +172,31 @@ export const getSecretsBotHelper = async ({
|
|||||||
ciphertext: secret.secretKeyCiphertext,
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
iv: secret.secretKeyIV,
|
iv: secret.secretKeyIV,
|
||||||
tag: secret.secretKeyTag,
|
tag: secret.secretKeyTag,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secret.secretValueCiphertext,
|
ciphertext: secret.secretValueCiphertext,
|
||||||
iv: secret.secretValueIV,
|
iv: secret.secretValueIV,
|
||||||
tag: secret.secretValueTag,
|
tag: secret.secretValueTag,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
content[secretKey] = secretValue;
|
content[secretKey] = { value: secretValue };
|
||||||
|
|
||||||
|
if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
|
||||||
|
const commentValue = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: secret.secretCommentCiphertext,
|
||||||
|
iv: secret.secretCommentIV,
|
||||||
|
tag: secret.secretCommentTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
content[secretKey].comment = commentValue;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await expandSecrets(workspaceId.toString(), key, content);
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -160,22 +207,18 @@ export const getSecretsBotHelper = async ({
|
|||||||
* @param {String} obj.workspaceId - id of workspace
|
* @param {String} obj.workspaceId - id of workspace
|
||||||
* @returns {String} key - decrypted workspace key
|
* @returns {String} key - decrypted workspace key
|
||||||
*/
|
*/
|
||||||
export const getKey = async ({
|
export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) => {
|
||||||
workspaceId,
|
|
||||||
}: {
|
|
||||||
workspaceId: Types.ObjectId;
|
|
||||||
}) => {
|
|
||||||
const encryptionKey = await getEncryptionKey();
|
const encryptionKey = await getEncryptionKey();
|
||||||
const rootEncryptionKey = await getRootEncryptionKey();
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
const botKey = await BotKey.findOne({
|
const botKey = await BotKey.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
}).populate<{ sender: IUser }>("sender", "publicKey");
|
}).populate<{ sender: IUser }>("sender", "publicKey");
|
||||||
|
|
||||||
if (!botKey) throw new Error("Failed to find bot key");
|
if (!botKey) throw new Error("Failed to find bot key");
|
||||||
|
|
||||||
const bot = await Bot.findOne({
|
const bot = await Bot.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
}).select("+encryptedPrivateKey +iv +tag +algorithm +keyEncoding");
|
}).select("+encryptedPrivateKey +iv +tag +algorithm +keyEncoding");
|
||||||
|
|
||||||
if (!bot) throw new Error("Failed to find bot");
|
if (!bot) throw new Error("Failed to find bot");
|
||||||
@@ -194,7 +237,7 @@ export const getKey = async ({
|
|||||||
ciphertext: botKey.encryptedKey,
|
ciphertext: botKey.encryptedKey,
|
||||||
nonce: botKey.nonce,
|
nonce: botKey.nonce,
|
||||||
publicKey: botKey.sender.publicKey as string,
|
publicKey: botKey.sender.publicKey as string,
|
||||||
privateKey: privateKeyBot,
|
privateKey: privateKeyBot
|
||||||
});
|
});
|
||||||
} else if (encryptionKey && bot.keyEncoding === ENCODING_SCHEME_UTF8) {
|
} else if (encryptionKey && bot.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||||
// case: encoding scheme is utf8
|
// case: encoding scheme is utf8
|
||||||
@@ -202,20 +245,19 @@ export const getKey = async ({
|
|||||||
ciphertext: bot.encryptedPrivateKey,
|
ciphertext: bot.encryptedPrivateKey,
|
||||||
iv: bot.iv,
|
iv: bot.iv,
|
||||||
tag: bot.tag,
|
tag: bot.tag,
|
||||||
key: encryptionKey,
|
key: encryptionKey
|
||||||
});
|
});
|
||||||
|
|
||||||
return decryptAsymmetric({
|
return decryptAsymmetric({
|
||||||
ciphertext: botKey.encryptedKey,
|
ciphertext: botKey.encryptedKey,
|
||||||
nonce: botKey.nonce,
|
nonce: botKey.nonce,
|
||||||
publicKey: botKey.sender.publicKey as string,
|
publicKey: botKey.sender.publicKey as string,
|
||||||
privateKey: privateKeyBot,
|
privateKey: privateKeyBot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw InternalServerError({
|
throw InternalServerError({
|
||||||
message:
|
message: "Failed to obtain bot's copy of workspace key needed for bot operations"
|
||||||
"Failed to obtain bot's copy of workspace key needed for bot operations",
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -228,7 +270,7 @@ export const getKey = async ({
|
|||||||
*/
|
*/
|
||||||
export const encryptSymmetricHelper = async ({
|
export const encryptSymmetricHelper = async ({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
plaintext,
|
plaintext
|
||||||
}: {
|
}: {
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
plaintext: string;
|
plaintext: string;
|
||||||
@@ -236,13 +278,13 @@ export const encryptSymmetricHelper = async ({
|
|||||||
const key = await getKey({ workspaceId: workspaceId });
|
const key = await getKey({ workspaceId: workspaceId });
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext,
|
plaintext,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
@@ -258,7 +300,7 @@ export const decryptSymmetricHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag
|
||||||
}: {
|
}: {
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
ciphertext: string;
|
ciphertext: string;
|
||||||
@@ -270,7 +312,7 @@ export const decryptSymmetricHelper = async ({
|
|||||||
ciphertext,
|
ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
return plaintext;
|
return plaintext;
|
||||||
@@ -287,7 +329,7 @@ export const getSecretsCommentBotHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath
|
||||||
} : {
|
}: {
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
@@ -298,7 +340,7 @@ export const getSecretsCommentBotHelper = async ({
|
|||||||
let folderId = "root";
|
let folderId = "root";
|
||||||
const folders = await Folder.findOne({
|
const folders = await Folder.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!folders && secretPath !== "/") {
|
if (!folders && secretPath !== "/") {
|
||||||
@@ -317,23 +359,23 @@ export const getSecretsCommentBotHelper = async ({
|
|||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
type: SECRET_SHARED,
|
type: SECRET_SHARED,
|
||||||
folder: folderId,
|
folder: folderId
|
||||||
});
|
});
|
||||||
|
|
||||||
secrets.forEach((secret: ISecret) => {
|
secrets.forEach((secret: ISecret) => {
|
||||||
if(secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
|
if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
|
||||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secret.secretKeyCiphertext,
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
iv: secret.secretKeyIV,
|
iv: secret.secretKeyIV,
|
||||||
tag: secret.secretKeyTag,
|
tag: secret.secretKeyTag,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const commentValue = decryptSymmetric128BitHexKeyUTF8({
|
const commentValue = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secret.secretCommentCiphertext,
|
ciphertext: secret.secretCommentCiphertext,
|
||||||
iv: secret.secretCommentIV,
|
iv: secret.secretCommentIV,
|
||||||
tag: secret.secretCommentTag,
|
tag: secret.secretCommentTag,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
content[secretKey] = commentValue;
|
content[secretKey] = commentValue;
|
||||||
@@ -341,4 +383,4 @@ export const getSecretsCommentBotHelper = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import { UnauthorizedRequestError } from "../utils/errors";
|
import { UnauthorizedRequestError } from "../utils/errors";
|
||||||
import * as Sentry from "@sentry/node";
|
import * as Sentry from "@sentry/node";
|
||||||
@@ -34,7 +34,7 @@ export const handleOAuthExchangeHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
integration,
|
integration,
|
||||||
code,
|
code,
|
||||||
environment,
|
environment
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
integration: string;
|
integration: string;
|
||||||
@@ -43,21 +43,20 @@ export const handleOAuthExchangeHelper = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const bot = await Bot.findOne({
|
const bot = await Bot.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
isActive: true,
|
isActive: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!bot)
|
if (!bot) throw new Error("Bot must be enabled for OAuth2 code-token exchange");
|
||||||
throw new Error("Bot must be enabled for OAuth2 code-token exchange");
|
|
||||||
|
|
||||||
// exchange code for access and refresh tokens
|
// exchange code for access and refresh tokens
|
||||||
const res = await exchangeCode({
|
const res = await exchangeCode({
|
||||||
integration,
|
integration,
|
||||||
code,
|
code
|
||||||
});
|
});
|
||||||
|
|
||||||
const update: Update = {
|
const update: Update = {
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
integration,
|
integration
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (integration) {
|
switch (integration) {
|
||||||
@@ -72,12 +71,12 @@ export const handleOAuthExchangeHelper = async ({
|
|||||||
const integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
const integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
integration,
|
integration
|
||||||
},
|
},
|
||||||
update,
|
update,
|
||||||
{
|
{
|
||||||
new: true,
|
new: true,
|
||||||
upsert: true,
|
upsert: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -86,7 +85,7 @@ export const handleOAuthExchangeHelper = async ({
|
|||||||
// set integration auth refresh token
|
// set integration auth refresh token
|
||||||
await setIntegrationAuthRefreshHelper({
|
await setIntegrationAuthRefreshHelper({
|
||||||
integrationAuthId: integrationAuth._id.toString(),
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
refreshToken: res.refreshToken,
|
refreshToken: res.refreshToken
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +96,7 @@ export const handleOAuthExchangeHelper = async ({
|
|||||||
integrationAuthId: integrationAuth._id.toString(),
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
accessId: null,
|
accessId: null,
|
||||||
accessToken: res.accessToken,
|
accessToken: res.accessToken,
|
||||||
accessExpiresAt: res.accessExpiresAt,
|
accessExpiresAt: res.accessExpiresAt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +110,7 @@ export const handleOAuthExchangeHelper = async ({
|
|||||||
*/
|
*/
|
||||||
export const syncIntegrationsHelper = async ({
|
export const syncIntegrationsHelper = async ({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment
|
||||||
}: {
|
}: {
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
@@ -121,11 +120,11 @@ export const syncIntegrationsHelper = async ({
|
|||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
...(environment
|
...(environment
|
||||||
? {
|
? {
|
||||||
environment,
|
environment
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
isActive: true,
|
isActive: true,
|
||||||
app: { $ne: null },
|
app: { $ne: null }
|
||||||
});
|
});
|
||||||
|
|
||||||
// for each workspace integration, sync/push secrets
|
// for each workspace integration, sync/push secrets
|
||||||
@@ -135,25 +134,16 @@ export const syncIntegrationsHelper = async ({
|
|||||||
const secrets = await BotService.getSecrets({
|
const secrets = await BotService.getSecrets({
|
||||||
workspaceId: integration.workspace,
|
workspaceId: integration.workspace,
|
||||||
environment: integration.environment,
|
environment: integration.environment,
|
||||||
secretPath: integration.secretPath,
|
secretPath: integration.secretPath
|
||||||
});
|
});
|
||||||
|
|
||||||
// get workspace, environment (shared) secrets comments
|
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
|
||||||
const secretComments = await BotService.getSecretComments({
|
|
||||||
workspaceId: integration.workspace,
|
|
||||||
environment: integration.environment,
|
|
||||||
secretPath: integration.secretPath,
|
|
||||||
})
|
|
||||||
|
|
||||||
const integrationAuth = await IntegrationAuth.findById(
|
|
||||||
integration.integrationAuth
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||||
|
|
||||||
// get integration auth access token
|
// get integration auth access token
|
||||||
const access = await getIntegrationAuthAccessHelper({
|
const access = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: integration.integrationAuth,
|
integrationAuthId: integration.integrationAuth
|
||||||
});
|
});
|
||||||
|
|
||||||
// sync secrets to integration
|
// sync secrets to integration
|
||||||
@@ -162,14 +152,17 @@ export const syncIntegrationsHelper = async ({
|
|||||||
integrationAuth,
|
integrationAuth,
|
||||||
secrets,
|
secrets,
|
||||||
accessId: access.accessId === undefined ? null : access.accessId,
|
accessId: access.accessId === undefined ? null : access.accessId,
|
||||||
accessToken: access.accessToken,
|
accessToken: access.accessToken
|
||||||
secretComments
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
console.log(`syncIntegrationsHelper: failed with [workspaceId=${workspaceId}] [environment=${environment}]`, err) // eslint-disable-line no-use-before-define
|
// eslint-disable-next-line
|
||||||
throw err
|
console.log(
|
||||||
|
`syncIntegrationsHelper: failed with [workspaceId=${workspaceId}] [environment=${environment}]`,
|
||||||
|
err
|
||||||
|
); // eslint-disable-line no-use-before-define
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -182,24 +175,24 @@ export const syncIntegrationsHelper = async ({
|
|||||||
* @param {String} refreshToken - decrypted refresh token
|
* @param {String} refreshToken - decrypted refresh token
|
||||||
*/
|
*/
|
||||||
export const getIntegrationAuthRefreshHelper = async ({
|
export const getIntegrationAuthRefreshHelper = async ({
|
||||||
integrationAuthId,
|
integrationAuthId
|
||||||
}: {
|
}: {
|
||||||
integrationAuthId: Types.ObjectId;
|
integrationAuthId: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
const integrationAuth = await IntegrationAuth.findById(
|
const integrationAuth = await IntegrationAuth.findById(integrationAuthId).select(
|
||||||
integrationAuthId
|
"+refreshCiphertext +refreshIV +refreshTag"
|
||||||
).select("+refreshCiphertext +refreshIV +refreshTag");
|
);
|
||||||
|
|
||||||
if (!integrationAuth)
|
if (!integrationAuth)
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed to locate Integration Authentication credentials",
|
message: "Failed to locate Integration Authentication credentials"
|
||||||
});
|
});
|
||||||
|
|
||||||
const refreshToken = await BotService.decryptSymmetric({
|
const refreshToken = await BotService.decryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace,
|
workspaceId: integrationAuth.workspace,
|
||||||
ciphertext: integrationAuth.refreshCiphertext as string,
|
ciphertext: integrationAuth.refreshCiphertext as string,
|
||||||
iv: integrationAuth.refreshIV as string,
|
iv: integrationAuth.refreshIV as string,
|
||||||
tag: integrationAuth.refreshTag as string,
|
tag: integrationAuth.refreshTag as string
|
||||||
});
|
});
|
||||||
|
|
||||||
return refreshToken;
|
return refreshToken;
|
||||||
@@ -214,28 +207,26 @@ export const getIntegrationAuthRefreshHelper = async ({
|
|||||||
* @returns {String} accessToken - decrypted access token
|
* @returns {String} accessToken - decrypted access token
|
||||||
*/
|
*/
|
||||||
export const getIntegrationAuthAccessHelper = async ({
|
export const getIntegrationAuthAccessHelper = async ({
|
||||||
integrationAuthId,
|
integrationAuthId
|
||||||
}: {
|
}: {
|
||||||
integrationAuthId: Types.ObjectId;
|
integrationAuthId: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
let accessId;
|
let accessId;
|
||||||
let accessToken;
|
let accessToken;
|
||||||
const integrationAuth = await IntegrationAuth.findById(
|
const integrationAuth = await IntegrationAuth.findById(integrationAuthId).select(
|
||||||
integrationAuthId
|
|
||||||
).select(
|
|
||||||
"workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag"
|
"workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!integrationAuth)
|
if (!integrationAuth)
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed to locate Integration Authentication credentials",
|
message: "Failed to locate Integration Authentication credentials"
|
||||||
});
|
});
|
||||||
|
|
||||||
accessToken = await BotService.decryptSymmetric({
|
accessToken = await BotService.decryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace,
|
workspaceId: integrationAuth.workspace,
|
||||||
ciphertext: integrationAuth.accessCiphertext as string,
|
ciphertext: integrationAuth.accessCiphertext as string,
|
||||||
iv: integrationAuth.accessIV as string,
|
iv: integrationAuth.accessIV as string,
|
||||||
tag: integrationAuth.accessTag as string,
|
tag: integrationAuth.accessTag as string
|
||||||
});
|
});
|
||||||
|
|
||||||
if (integrationAuth?.accessExpiresAt && integrationAuth?.refreshCiphertext) {
|
if (integrationAuth?.accessExpiresAt && integrationAuth?.refreshCiphertext) {
|
||||||
@@ -245,11 +236,11 @@ export const getIntegrationAuthAccessHelper = async ({
|
|||||||
if (integrationAuth.accessExpiresAt < new Date()) {
|
if (integrationAuth.accessExpiresAt < new Date()) {
|
||||||
// access token is expired
|
// access token is expired
|
||||||
const refreshToken = await getIntegrationAuthRefreshHelper({
|
const refreshToken = await getIntegrationAuthRefreshHelper({
|
||||||
integrationAuthId,
|
integrationAuthId
|
||||||
});
|
});
|
||||||
accessToken = await exchangeRefresh({
|
accessToken = await exchangeRefresh({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
refreshToken,
|
refreshToken
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,13 +254,13 @@ export const getIntegrationAuthAccessHelper = async ({
|
|||||||
workspaceId: integrationAuth.workspace,
|
workspaceId: integrationAuth.workspace,
|
||||||
ciphertext: integrationAuth.accessIdCiphertext as string,
|
ciphertext: integrationAuth.accessIdCiphertext as string,
|
||||||
iv: integrationAuth.accessIdIV as string,
|
iv: integrationAuth.accessIdIV as string,
|
||||||
tag: integrationAuth.accessIdTag as string,
|
tag: integrationAuth.accessIdTag as string
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accessId,
|
accessId,
|
||||||
accessToken,
|
accessToken
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -283,7 +274,7 @@ export const getIntegrationAuthAccessHelper = async ({
|
|||||||
*/
|
*/
|
||||||
export const setIntegrationAuthRefreshHelper = async ({
|
export const setIntegrationAuthRefreshHelper = async ({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
refreshToken,
|
refreshToken
|
||||||
}: {
|
}: {
|
||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
@@ -294,22 +285,22 @@ export const setIntegrationAuthRefreshHelper = async ({
|
|||||||
|
|
||||||
const obj = await BotService.encryptSymmetric({
|
const obj = await BotService.encryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace,
|
workspaceId: integrationAuth.workspace,
|
||||||
plaintext: refreshToken,
|
plaintext: refreshToken
|
||||||
});
|
});
|
||||||
|
|
||||||
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
_id: integrationAuthId,
|
_id: integrationAuthId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
refreshCiphertext: obj.ciphertext,
|
refreshCiphertext: obj.ciphertext,
|
||||||
refreshIV: obj.iv,
|
refreshIV: obj.iv,
|
||||||
refreshTag: obj.tag,
|
refreshTag: obj.tag,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true,
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -329,7 +320,7 @@ export const setIntegrationAuthAccessHelper = async ({
|
|||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
accessId,
|
accessId,
|
||||||
accessToken,
|
accessToken,
|
||||||
accessExpiresAt,
|
accessExpiresAt
|
||||||
}: {
|
}: {
|
||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
accessId: string | null;
|
accessId: string | null;
|
||||||
@@ -342,20 +333,20 @@ export const setIntegrationAuthAccessHelper = async ({
|
|||||||
|
|
||||||
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace,
|
workspaceId: integrationAuth.workspace,
|
||||||
plaintext: accessToken,
|
plaintext: accessToken
|
||||||
});
|
});
|
||||||
|
|
||||||
let encryptedAccessIdObj;
|
let encryptedAccessIdObj;
|
||||||
if (accessId) {
|
if (accessId) {
|
||||||
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace,
|
workspaceId: integrationAuth.workspace,
|
||||||
plaintext: accessId,
|
plaintext: accessId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
_id: integrationAuthId,
|
_id: integrationAuthId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
||||||
@@ -366,10 +357,10 @@ export const setIntegrationAuthAccessHelper = async ({
|
|||||||
accessTag: encryptedAccessTokenObj.tag,
|
accessTag: encryptedAccessTokenObj.tag,
|
||||||
accessExpiresAt,
|
accessExpiresAt,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true,
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -42,9 +42,10 @@ import { TelemetryService } from "../services";
|
|||||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||||
import { EELogService, EESecretService } from "../ee/services";
|
import { EELogService, EESecretService } from "../ee/services";
|
||||||
import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/auth";
|
import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/auth";
|
||||||
import { getFolderIdFromServiceToken } from "../services/FolderService";
|
import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import Folder, { TFolderRootSchema } from "../models/folder";
|
||||||
|
|
||||||
export const isValidScope = (
|
export const isValidScope = (
|
||||||
authPayload: IServiceTokenData,
|
authPayload: IServiceTokenData,
|
||||||
@@ -64,10 +65,9 @@ export const isValidScope = (
|
|||||||
export function containsGlobPatterns(secretPath: string) {
|
export function containsGlobPatterns(secretPath: string) {
|
||||||
const globChars = ["*", "?", "[", "]", "{", "}", "**"];
|
const globChars = ["*", "?", "[", "]", "{", "}", "**"];
|
||||||
const normalizedPath = path.normalize(secretPath);
|
const normalizedPath = path.normalize(secretPath);
|
||||||
return globChars.some(char => normalizedPath.includes(char));
|
return globChars.some((char) => normalizedPath.includes(char));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object containing secret [secret] but with its value, key, comment decrypted.
|
* Returns an object containing secret [secret] but with its value, key, comment decrypted.
|
||||||
*
|
*
|
||||||
@@ -929,3 +929,164 @@ export const deleteSecretHelper = async ({
|
|||||||
secret
|
secret
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchSecretsCrossEnv = (workspaceId: string, folders: TFolderRootSchema[], key: string) => {
|
||||||
|
const fetchCache: Record<string, Record<string, string>> = {};
|
||||||
|
|
||||||
|
return async (secRefEnv: string, secRefPath: string[], secRefKey: string) => {
|
||||||
|
const secRefPathUrl = path.join("/", ...secRefPath);
|
||||||
|
const uniqKey = `${secRefEnv}-${secRefPathUrl}`;
|
||||||
|
|
||||||
|
if (fetchCache?.[uniqKey]) {
|
||||||
|
return fetchCache[uniqKey][secRefKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
let folderId = "root";
|
||||||
|
const folder = folders.find(({ environment }) => environment === secRefEnv);
|
||||||
|
if (!folder && secRefPathUrl !== "/") {
|
||||||
|
throw BadRequestError({ message: "Folder not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folder) {
|
||||||
|
const selectedFolder = getFolderByPath(folder.nodes, secRefPathUrl);
|
||||||
|
if (!selectedFolder) {
|
||||||
|
throw BadRequestError({ message: "Folder not found" });
|
||||||
|
}
|
||||||
|
folderId = selectedFolder.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const secrets = await Secret.find({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment: secRefEnv,
|
||||||
|
type: SECRET_SHARED,
|
||||||
|
folder: folderId
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedSec = secrets.reduce<Record<string, string>>((prev, secret) => {
|
||||||
|
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
|
iv: secret.secretKeyIV,
|
||||||
|
tag: secret.secretKeyTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: secret.secretValueCiphertext,
|
||||||
|
iv: secret.secretValueIV,
|
||||||
|
tag: secret.secretValueTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
prev[secretKey] = secretValue;
|
||||||
|
return prev;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
fetchCache[uniqKey] = decryptedSec;
|
||||||
|
|
||||||
|
return fetchCache[uniqKey][secRefKey];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const INTERPOLATION_SYNTAX_REG = new RegExp(/\${([^}]+)}/g);
|
||||||
|
const recursivelyExpandSecret = async (
|
||||||
|
expandedSec: Record<string, string>,
|
||||||
|
interpolatedSec: Record<string, string>,
|
||||||
|
fetchCrossEnv: (env: string, secPath: string[], secKey: string) => Promise<string>,
|
||||||
|
recursionChainBreaker: Record<string, boolean>,
|
||||||
|
key: string
|
||||||
|
) => {
|
||||||
|
if (expandedSec?.[key]) {
|
||||||
|
return expandedSec[key];
|
||||||
|
}
|
||||||
|
if (recursionChainBreaker?.[key]) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
recursionChainBreaker[key] = true;
|
||||||
|
|
||||||
|
let interpolatedValue = interpolatedSec[key];
|
||||||
|
if (!interpolatedValue) {
|
||||||
|
throw new Error(`Couldn't find referenced value - ${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const refs = interpolatedValue.match(INTERPOLATION_SYNTAX_REG);
|
||||||
|
if (refs) {
|
||||||
|
for (const interpolationSyntax of refs) {
|
||||||
|
const interpolationKey = interpolationSyntax.slice(2, interpolationSyntax.length - 1);
|
||||||
|
const entities = interpolationKey.trim().split(".");
|
||||||
|
|
||||||
|
if (entities.length === 1) {
|
||||||
|
const val = await recursivelyExpandSecret(
|
||||||
|
expandedSec,
|
||||||
|
interpolatedSec,
|
||||||
|
fetchCrossEnv,
|
||||||
|
recursionChainBreaker,
|
||||||
|
interpolationKey
|
||||||
|
);
|
||||||
|
if (val) {
|
||||||
|
interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entities.length > 1) {
|
||||||
|
const secRefEnv = entities[0];
|
||||||
|
const secRefPath = entities.slice(1, entities.length - 1);
|
||||||
|
const secRefKey = entities[entities.length - 1];
|
||||||
|
|
||||||
|
const val = await fetchCrossEnv(secRefEnv, secRefPath, secRefKey);
|
||||||
|
interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expandedSec[key] = interpolatedValue;
|
||||||
|
return interpolatedValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// used to convert multi line ones to quotes ones with \n
|
||||||
|
const formatMultiValueEnv = (val?: string) => {
|
||||||
|
if (!val) return "";
|
||||||
|
if (!val.match("\n")) return val;
|
||||||
|
return `"${val.replace(/\n/g, "\\n")}"`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const expandSecrets = async (
|
||||||
|
workspaceId: string,
|
||||||
|
rootEncKey: string,
|
||||||
|
secrets: Record<string, { value: string; comment?: string }>
|
||||||
|
) => {
|
||||||
|
const expandedSec: Record<string, string> = {};
|
||||||
|
const interpolatedSec: Record<string, string> = {};
|
||||||
|
|
||||||
|
const folders = await Folder.find({ workspace: workspaceId });
|
||||||
|
const crossSecEnvFetch = fetchSecretsCrossEnv(workspaceId, folders, rootEncKey);
|
||||||
|
|
||||||
|
Object.keys(secrets).forEach((key) => {
|
||||||
|
if (secrets[key].value.match(INTERPOLATION_SYNTAX_REG)) {
|
||||||
|
interpolatedSec[key] = secrets[key].value;
|
||||||
|
} else {
|
||||||
|
expandedSec[key] = secrets[key].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const key of Object.keys(secrets)) {
|
||||||
|
if (expandedSec?.[key]) {
|
||||||
|
secrets[key].value = formatMultiValueEnv(expandedSec[key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is to avoid recursion loop. So the graph should be direct graph rather than cyclic
|
||||||
|
// so for any recursion building if there is an entity two times same key meaning it will be looped
|
||||||
|
const recursionChainBreaker: Record<string, boolean> = {};
|
||||||
|
const expandedVal = await recursivelyExpandSecret(
|
||||||
|
expandedSec,
|
||||||
|
interpolatedSec,
|
||||||
|
crossSecEnvFetch,
|
||||||
|
recursionChainBreaker,
|
||||||
|
key
|
||||||
|
);
|
||||||
|
|
||||||
|
secrets[key].value = formatMultiValueEnv(expandedVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
};
|
||||||
|
|||||||
+665
-649
File diff suppressed because it is too large
Load Diff
@@ -57,7 +57,7 @@ router.get(
|
|||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
checkIPAllowlist: false
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretByNameRaw
|
secretsController.getSecretByNameRaw
|
||||||
);
|
);
|
||||||
@@ -86,7 +86,7 @@ router.post(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
checkIPAllowlist: false
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.createSecretRaw
|
secretsController.createSecretRaw
|
||||||
);
|
);
|
||||||
@@ -115,7 +115,7 @@ router.patch(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
checkIPAllowlist: false
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.updateSecretByNameRaw
|
secretsController.updateSecretByNameRaw
|
||||||
);
|
);
|
||||||
@@ -143,7 +143,7 @@ router.delete(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
checkIPAllowlist: false
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.deleteSecretByNameRaw
|
secretsController.deleteSecretByNameRaw
|
||||||
);
|
);
|
||||||
@@ -169,7 +169,7 @@ router.get(
|
|||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
checkIPAllowlist: false
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.getSecrets
|
secretsController.getSecrets
|
||||||
);
|
);
|
||||||
@@ -205,7 +205,7 @@ router.post(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
checkIPAllowlist: false
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.createSecret
|
secretsController.createSecret
|
||||||
);
|
);
|
||||||
@@ -232,7 +232,7 @@ router.get(
|
|||||||
locationEnvironment: "query",
|
locationEnvironment: "query",
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
checkIPAllowlist: false
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretByName
|
secretsController.getSecretByName
|
||||||
);
|
);
|
||||||
@@ -263,7 +263,7 @@ router.patch(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
checkIPAllowlist: false
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.updateSecretByName
|
secretsController.updateSecretByName
|
||||||
);
|
);
|
||||||
@@ -291,7 +291,7 @@ router.delete(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
checkIPAllowlist: false
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.deleteSecretByName
|
secretsController.deleteSecretByName
|
||||||
);
|
);
|
||||||
|
|||||||
+4
-13
@@ -1,9 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2016",
|
"target": "es2016",
|
||||||
"lib": [
|
"lib": ["es6", "es2021"],
|
||||||
"es6"
|
|
||||||
],
|
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
@@ -15,15 +13,8 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"typeRoots": [
|
"typeRoots": ["./src/types", "./node_modules/@types"]
|
||||||
"./src/types",
|
|
||||||
"./node_modules/@types"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*"],
|
||||||
"src/**/*"
|
"exclude": ["node_modules"]
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ func CallGetAccessibleEnvironments(httpClient *resty.Client, request GetAccessib
|
|||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetAccessibleEnvironmentsResponse{}, fmt.Errorf("CallGetAccessibleEnvironments: Unsuccessful response: [response=%v]", response)
|
return GetAccessibleEnvironmentsResponse{}, fmt.Errorf("CallGetAccessibleEnvironments: Unsuccessful response: [response=%v] [response-code=%v] [url=%s]", response, response.StatusCode(), response.Request.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
return accessibleEnvironmentsResponse, nil
|
return accessibleEnvironmentsResponse, nil
|
||||||
|
|||||||
+2
-1
@@ -23,7 +23,8 @@
|
|||||||
},
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"suggestEdit": true,
|
"suggestEdit": true,
|
||||||
"raiseIssue": true
|
"raiseIssue": true,
|
||||||
|
"thumbsRating": true
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"baseUrl": ["https://app.infisical.com", "http://localhost:8080"],
|
"baseUrl": ["https://app.infisical.com", "http://localhost:8080"],
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ description: "Use our Helm chart to Install Infisical on your Kubernetes cluster
|
|||||||
- Installed [Helm package manager](https://helm.sh/) version v3.11.3 or greater
|
- Installed [Helm package manager](https://helm.sh/) version v3.11.3 or greater
|
||||||
- You have [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) installed and connected to your kubernetes cluster
|
- You have [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) installed and connected to your kubernetes cluster
|
||||||
|
|
||||||
<iframe width="100%" height="375" src="https://www.youtube.com/embed/ugJZSCcZaV8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
|
||||||
|
|
||||||
|
|
||||||
By deploying Infisical on Kubernetes, you can take advantage of its features to ensure that the application is fault-tolerant, highly available, and scalable.
|
By deploying Infisical on Kubernetes, you can take advantage of its features to ensure that the application is fault-tolerant, highly available, and scalable.
|
||||||
To make the installation process easier and more streamlined, we have created a Helm chart that you can use to install Infisical on Kubernetes.
|
To make the installation process easier and more streamlined, we have created a Helm chart that you can use to install Infisical on Kubernetes.
|
||||||
|
|
||||||
@@ -34,10 +31,11 @@ By default, the application will use the latest tag to retrieve the required Doc
|
|||||||
However, it's important to specify a particular version of Infisical during installation to prevent any significant updates from disrupting your deployment.
|
However, it's important to specify a particular version of Infisical during installation to prevent any significant updates from disrupting your deployment.
|
||||||
View [properties for frontend and backend](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical#parameters).
|
View [properties for frontend and backend](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical#parameters).
|
||||||
|
|
||||||
|
<Tip>
|
||||||
To determine the appropriate versions to use for the docker images, follow the links bellow
|
To find the latest version number of Infisical, follow the links bellow
|
||||||
- [frontend Docker image](https://hub.docker.com/r/infisical/frontend/tags)
|
- [frontend Docker image](https://hub.docker.com/r/infisical/frontend/tags)
|
||||||
- [backend Docker image](https://hub.docker.com/r/infisical/backend/tags)
|
- [backend Docker image](https://hub.docker.com/r/infisical/backend/tags)
|
||||||
|
</Tip>
|
||||||
|
|
||||||
```yaml simple-values-example.yaml
|
```yaml simple-values-example.yaml
|
||||||
frontend:
|
frontend:
|
||||||
@@ -45,14 +43,14 @@ frontend:
|
|||||||
replicaCount: 2
|
replicaCount: 2
|
||||||
image:
|
image:
|
||||||
repository: infisical/frontend
|
repository: infisical/frontend
|
||||||
tag: "v0.1.3"
|
tag: "v0.26.0" # <--- frontend version
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
replicaCount: 2
|
replicaCount: 2
|
||||||
image:
|
image:
|
||||||
repository: infisical/backend
|
repository: infisical/backend
|
||||||
tag: "v0.1.3"
|
tag: "v0.26.0" # <--- backend version
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { forwardRef, InputHTMLAttributes, ReactNode } from "react";
|
import { ChangeEvent, forwardRef, InputHTMLAttributes, ReactNode } from "react";
|
||||||
import { cva, VariantProps } from "cva";
|
import { cva, VariantProps } from "cva";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ type Props = {
|
|||||||
rightIcon?: ReactNode;
|
rightIcon?: ReactNode;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
isReadOnly?: boolean;
|
isReadOnly?: boolean;
|
||||||
|
autoCapitalization?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const inputVariants = cva(
|
const inputVariants = cva(
|
||||||
@@ -80,10 +81,19 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
variant = "filled",
|
variant = "filled",
|
||||||
size = "md",
|
size = "md",
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
|
autoCapitalization,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
|
const handleInput = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
console.log(123, props, autoCapitalization)
|
||||||
|
if (autoCapitalization) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
event.target.value = event.target.value.toUpperCase();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={inputParentContainerVariants({ isRounded, isError, isFullWidth, variant })}>
|
<div className={inputParentContainerVariants({ isRounded, isError, isFullWidth, variant })}>
|
||||||
{leftIcon && <span className="absolute left-0 ml-3 text-sm">{leftIcon}</span>}
|
{leftIcon && <span className="absolute left-0 ml-3 text-sm">{leftIcon}</span>}
|
||||||
@@ -93,6 +103,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
|
onInput={handleInput}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
leftIcon ? "pl-10" : "pl-2.5",
|
leftIcon ? "pl-10" : "pl-2.5",
|
||||||
rightIcon ? "pr-10" : "pr-2.5",
|
rightIcon ? "pr-10" : "pr-2.5",
|
||||||
|
|||||||
@@ -47,9 +47,10 @@ const ActivityLogsRow = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const renderUser = () => {
|
const renderUser = () => {
|
||||||
|
|
||||||
if (row?.user) return `${row.user}`;
|
if (row?.user) return `${row.user}`;
|
||||||
if (row?.serviceAccount) return `Service Account: ${row.serviceAccount.name}`;
|
if (row?.serviceAccount) return `Service Account: ${row.serviceAccount.name}`;
|
||||||
if (row?.serviceTokenData.name) return `Service Token: ${row.serviceTokenData.name}`;
|
if (row?.serviceTokenData?.name) return `Service Token: ${row.serviceTokenData.name}`;
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export {
|
|||||||
useGetOrganization,
|
useGetOrganization,
|
||||||
useGetOrgBillingDetails,
|
useGetOrgBillingDetails,
|
||||||
useGetOrgInvoices,
|
useGetOrgInvoices,
|
||||||
|
useGetOrgLicenses,
|
||||||
useGetOrgPlanBillingInfo,
|
useGetOrgPlanBillingInfo,
|
||||||
useGetOrgPlansTable,
|
useGetOrgPlansTable,
|
||||||
useGetOrgPlanTable,
|
useGetOrgPlanTable,
|
||||||
@@ -14,5 +15,4 @@ export {
|
|||||||
useGetOrgTaxIds,
|
useGetOrgTaxIds,
|
||||||
useGetOrgTrialUrl,
|
useGetOrgTrialUrl,
|
||||||
useRenameOrg,
|
useRenameOrg,
|
||||||
useUpdateOrgBillingDetails
|
useUpdateOrgBillingDetails} from "./queries";
|
||||||
} from "./queries";
|
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import { apiRequest } from "@app/config/request";
|
|||||||
import {
|
import {
|
||||||
BillingDetails,
|
BillingDetails,
|
||||||
Invoice,
|
Invoice,
|
||||||
|
License,
|
||||||
Organization,
|
Organization,
|
||||||
OrgPlanTable,
|
OrgPlanTable,
|
||||||
PlanBillingInfo,
|
PlanBillingInfo,
|
||||||
PmtMethod,
|
PmtMethod,
|
||||||
ProductsTable,
|
ProductsTable,
|
||||||
RenameOrgDTO,
|
RenameOrgDTO,
|
||||||
TaxID
|
TaxID} from "./types";
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
const organizationKeys = {
|
const organizationKeys = {
|
||||||
getUserOrganization: ["organization"] as const,
|
getUserOrganization: ["organization"] as const,
|
||||||
@@ -23,6 +23,7 @@ const organizationKeys = {
|
|||||||
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,
|
||||||
getOrgInvoices: (orgId: string) => [{ orgId }, "organization-invoices"] as const,
|
getOrgInvoices: (orgId: string) => [{ orgId }, "organization-invoices"] as const,
|
||||||
|
getOrgLicenses: (orgId: string) => [{ orgId }, "organization-licenses"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetOrganization = () => {
|
export const useGetOrganization = () => {
|
||||||
@@ -312,3 +313,19 @@ export const useCreateCustomerPortalSession = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetOrgLicenses = (organizationId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: organizationKeys.getOrgLicenses(organizationId),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (organizationId === "") return undefined;
|
||||||
|
|
||||||
|
const { data } = await apiRequest.get<License[]>(
|
||||||
|
`/api/v1/organizations/${organizationId}/licenses`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -49,6 +49,17 @@ export type TaxID = {
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type License = {
|
||||||
|
_id: string;
|
||||||
|
customerId: string;
|
||||||
|
prefix: string;
|
||||||
|
licenseKey: string;
|
||||||
|
isActivated: boolean;
|
||||||
|
expiresAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type OrgPlanTableHead = {
|
export type OrgPlanTableHead = {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -971,6 +971,7 @@ export const DashboardPage = () => {
|
|||||||
register={register}
|
register={register}
|
||||||
control={control}
|
control={control}
|
||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
|
autoCapitalization={currentWorkspace?.autoCapitalization}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{!isReadOnly && !isRollbackMode && (
|
{!isReadOnly && !isRollbackMode && (
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ type Props = {
|
|||||||
setValue: UseFormSetValue<FormData>;
|
setValue: UseFormSetValue<FormData>;
|
||||||
isKeyError?: boolean;
|
isKeyError?: boolean;
|
||||||
keyError?: string;
|
keyError?: string;
|
||||||
|
autoCapitalization?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretInputRow = memo(
|
export const SecretInputRow = memo(
|
||||||
@@ -98,7 +99,8 @@ export const SecretInputRow = memo(
|
|||||||
setValue,
|
setValue,
|
||||||
isKeyError,
|
isKeyError,
|
||||||
keyError,
|
keyError,
|
||||||
secUniqId
|
secUniqId,
|
||||||
|
autoCapitalization
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const isKeySubDisabled = useRef<boolean>(false);
|
const isKeySubDisabled = useRef<boolean>(false);
|
||||||
// comment management in a row
|
// comment management in a row
|
||||||
@@ -243,6 +245,7 @@ export const SecretInputRow = memo(
|
|||||||
isKeySubDisabled.current = false;
|
isKeySubDisabled.current = false;
|
||||||
field.onBlur();
|
field.onBlur();
|
||||||
}}
|
}}
|
||||||
|
autoCapitalization={autoCapitalization}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ export const CurrentPlanSection = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<h2 className="mb-8 flex-1 text-xl font-semibold text-white">Current Usage</h2>
|
<h2 className="mb-8 flex-1 text-xl font-semibold text-white">Current usage</h2>
|
||||||
<TableContainer className="mt-4">
|
<TableContainer className="mt-4">
|
||||||
<Table>
|
<Table>
|
||||||
<THead>
|
<THead>
|
||||||
|
|||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
import { LicensesSection } from "./LicensesSection";
|
||||||
|
|
||||||
|
export const BillingSelfHostedTab = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<LicensesSection />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
import { faFileContract } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
import {
|
||||||
|
EmptyState,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useOrganization } from "@app/context";
|
||||||
|
import {
|
||||||
|
useGetOrgLicenses
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
|
export const LicensesSection = () => {
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const { data, isLoading } = useGetOrgLicenses(currentOrg?._id ?? "");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<h2 className="mb-8 flex-1 text-xl font-semibold text-white">Enterprise licenses</h2>
|
||||||
|
<TableContainer className="mt-4">
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>License Key</Th>
|
||||||
|
<Th>Status</Th>
|
||||||
|
<Th>Issued Date</Th>
|
||||||
|
<Th>Expiry Date</Th>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{!isLoading && data && data?.length > 0 && data.map(({
|
||||||
|
_id,
|
||||||
|
licenseKey,
|
||||||
|
isActivated,
|
||||||
|
createdAt,
|
||||||
|
expiresAt
|
||||||
|
}) => {
|
||||||
|
const formattedCreatedAt = new Date(createdAt).toISOString().split("T")[0];
|
||||||
|
const formattedExpiresAt = new Date(expiresAt).toISOString().split("T")[0];
|
||||||
|
return (
|
||||||
|
<Tr key={`license-${_id}`} className="h-10">
|
||||||
|
<Td>{licenseKey}</Td>
|
||||||
|
<Td>{isActivated ? "Active" : "Inactive"}</Td>
|
||||||
|
<Td>{formattedCreatedAt}</Td>
|
||||||
|
<Td>{formattedExpiresAt}</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{isLoading && <TableSkeleton columns={4} innerKey="licenses" />}
|
||||||
|
{!isLoading && data && data?.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={4}>
|
||||||
|
<EmptyState title="No enterprise licenses on file" icon={faFileContract} />
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./BillingSelfHostedTab";
|
||||||
+5
@@ -4,9 +4,11 @@ import { Tab } from "@headlessui/react"
|
|||||||
import { BillingCloudTab } from "../BillingCloudTab";
|
import { BillingCloudTab } from "../BillingCloudTab";
|
||||||
import { BillingDetailsTab } from "../BillingDetailsTab";
|
import { BillingDetailsTab } from "../BillingDetailsTab";
|
||||||
import { BillingReceiptsTab } from "../BillingReceiptsTab";
|
import { BillingReceiptsTab } from "../BillingReceiptsTab";
|
||||||
|
import { BillingSelfHostedTab } from "../BillingSelfHostedTab";
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: "Infisical Cloud", key: "tab-infisical-cloud" },
|
{ name: "Infisical Cloud", key: "tab-infisical-cloud" },
|
||||||
|
{ name: "Infisical Self-Hosted", key: "tab-infisical-self-hosted" },
|
||||||
{ name: "Receipts", key: "tab-receipts" },
|
{ name: "Receipts", key: "tab-receipts" },
|
||||||
{ name: "Billing details", key: "tab-billing-details" }
|
{ name: "Billing details", key: "tab-billing-details" }
|
||||||
];
|
];
|
||||||
@@ -32,6 +34,9 @@ export const BillingTabGroup = () => {
|
|||||||
<Tab.Panel>
|
<Tab.Panel>
|
||||||
<BillingCloudTab />
|
<BillingCloudTab />
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
|
<Tab.Panel>
|
||||||
|
<BillingSelfHostedTab />
|
||||||
|
</Tab.Panel>
|
||||||
<Tab.Panel>
|
<Tab.Panel>
|
||||||
<BillingReceiptsTab />
|
<BillingReceiptsTab />
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
|
|||||||
Reference in New Issue
Block a user