Merge branch 'main' into feat/create-multiple-orgs-under-same-account

This commit is contained in:
vmatsiiako
2023-10-16 18:55:48 -07:00
committed by GitHub
29 changed files with 484 additions and 104 deletions
-1
View File
@@ -133,7 +133,6 @@ Whether it's big or small, we love contributions. Check out our guide to see how
Not sure where to get started? You can: Not sure where to get started? You can:
- [Book a free, non-pressure pairing session / code walkthrough with one of our teammates](https://cal.com/tony-infisical/30-min-meeting-contributing)!
- Join our <a href="https://infisical.com/slack">Slack</a>, and ask us any questions there. - Join our <a href="https://infisical.com/slack">Slack</a>, and ask us any questions there.
- Join our [community calls](https://us06web.zoom.us/j/82623506356) every Wednesday at 11am EST to ask any questions, provide feedback, hangout and more. - Join our [community calls](https://us06web.zoom.us/j/82623506356) every Wednesday at 11am EST to ask any questions, provide feedback, hangout and more.
@@ -9,7 +9,6 @@ import { Secret, ServiceTokenData } from "../../models";
import { Folder } from "../../models/folder"; import { Folder } from "../../models/folder";
import { import {
appendFolder, appendFolder,
generateFolderId,
getAllFolderIds, getAllFolderIds,
getFolderByPath, getFolderByPath,
getFolderWithPathFromId, getFolderWithPathFromId,
@@ -132,9 +131,6 @@ export const createFolder = async (req: Request, res: Response) => {
// space has no folders initialized // space has no folders initialized
if (!folders) { if (!folders) {
if (directory !== "/") throw ERR_FOLDER_NOT_FOUND;
const id = generateFolderId();
const folder = new Folder({ const folder = new Folder({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
@@ -142,14 +138,15 @@ export const createFolder = async (req: Request, res: Response) => {
id: "root", id: "root",
name: "root", name: "root",
version: 1, version: 1,
children: [{ id, name: folderName, children: [], version: 1 }] children: []
} }
}); });
const { parent, child } = appendFolder(folder.nodes, { folderName, directory });
await folder.save(); await folder.save();
const folderVersion = new FolderVersion({ const folderVersion = new FolderVersion({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
nodes: folder.nodes nodes: parent
}); });
await folderVersion.save(); await folderVersion.save();
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
@@ -163,9 +160,9 @@ export const createFolder = async (req: Request, res: Response) => {
type: EventType.CREATE_FOLDER, type: EventType.CREATE_FOLDER,
metadata: { metadata: {
environment, environment,
folderId: id, folderId: child.id,
folderName, folderName,
folderPath: `root/${folderName}` folderPath: directory
} }
}, },
{ {
@@ -173,26 +170,26 @@ export const createFolder = async (req: Request, res: Response) => {
} }
); );
return res.json({ folder: { id, name: folderName } }); return res.json({ folder: { id: child.id, name: folderName } });
} }
const parentFolder = getFolderByPath(folders.nodes, directory); const { parent, child, hasCreated } = appendFolder(folders.nodes, { folderName, directory });
if (!parentFolder) throw ERR_FOLDER_NOT_FOUND;
if (!hasCreated) return res.json({ folder: child });
const folder = appendFolder(folders.nodes, { folderName, parentFolderId: parentFolder.id });
await Folder.findByIdAndUpdate(folders._id, folders); await Folder.findByIdAndUpdate(folders._id, folders);
const folderVersion = new FolderVersion({ const folderVersion = new FolderVersion({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
nodes: parentFolder nodes: parent
}); });
await folderVersion.save(); await folderVersion.save();
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
folderId: parentFolder.id folderId: child.id
}); });
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
@@ -201,7 +198,7 @@ export const createFolder = async (req: Request, res: Response) => {
type: EventType.CREATE_FOLDER, type: EventType.CREATE_FOLDER,
metadata: { metadata: {
environment, environment,
folderId: folder.id, folderId: child.id,
folderName, folderName,
folderPath: directory folderPath: directory
} }
@@ -211,7 +208,7 @@ export const createFolder = async (req: Request, res: Response) => {
} }
); );
return res.json({ folder }); return res.json({ folder: child });
}; };
/** /**
@@ -777,7 +777,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
*/ */
export const deleteSecretByName = async (req: Request, res: Response) => { export const deleteSecretByName = async (req: Request, res: Response) => {
const { const {
body: { type, environment, secretPath, workspaceId }, body: { type, environment, secretPath, workspaceId, secretId },
params: { secretName } params: { secretName }
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req); } = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
@@ -813,6 +813,7 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
const { secret } = await SecretService.deleteSecret({ const { secret } = await SecretService.deleteSecret({
secretName, secretName,
secretId,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
type, type,
+101 -8
View File
@@ -1,4 +1,4 @@
import mongoose, { Types } from "mongoose"; import mongoose, { Types, mongo } from "mongoose";
import { import {
Bot, Bot,
BotKey, BotKey,
@@ -111,48 +111,78 @@ export const createOrganization = async ({
* @returns * @returns
*/ */
export const deleteOrganization = async ({ export const deleteOrganization = async ({
organizationId organizationId,
existingSession
}: { }: {
organizationId: Types.ObjectId; organizationId: Types.ObjectId;
existingSession?: mongo.ClientSession;
}) => { }) => {
const session = await mongoose.startSession();
session.startTransaction(); let session;
if (existingSession) {
session = existingSession;
} else {
session = await mongoose.startSession();
session.startTransaction();
}
try { try {
const organization = await Organization.findByIdAndDelete(organizationId); const organization = await Organization.findByIdAndDelete(
organizationId,
{
session
}
);
if (!organization) throw ResourceNotFoundError(); if (!organization) throw ResourceNotFoundError();
await MembershipOrg.deleteMany({ await MembershipOrg.deleteMany({
organization: organization._id organization: organization._id
}, {
session
}); });
await BotOrg.deleteMany({ await BotOrg.deleteMany({
organization: organization._id organization: organization._id
}, {
session
}); });
await SSOConfig.deleteMany({ await SSOConfig.deleteMany({
organization: organization._id organization: organization._id
}, {
session
}); });
await Role.deleteMany({ await Role.deleteMany({
organization: organization._id organization: organization._id
}, {
session
}); });
await IncidentContactOrg.deleteMany({ await IncidentContactOrg.deleteMany({
organization: organization._id organization: organization._id
}, {
session
}); });
await GitRisks.deleteMany({ await GitRisks.deleteMany({
organization: organization._id organization: organization._id
}, {
session
}); });
await GitAppInstallationSession.deleteMany({ await GitAppInstallationSession.deleteMany({
organization: organization._id organization: organization._id
}, {
session
}); });
await GitAppOrganizationInstallation.deleteMany({ await GitAppOrganizationInstallation.deleteMany({
organization: organization._id organization: organization._id
}, {
session
}); });
const workspaceIds = await Workspace.distinct("_id", { const workspaceIds = await Workspace.distinct("_id", {
@@ -161,167 +191,230 @@ export const deleteOrganization = async ({
await Workspace.deleteMany({ await Workspace.deleteMany({
organization: organization._id organization: organization._id
}, {
session
}); });
await Membership.deleteMany({ await Membership.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await Key.deleteMany({ await Key.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await Bot.deleteMany({ await Bot.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await BotKey.deleteMany({ await BotKey.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await SecretBlindIndexData.deleteMany({ await SecretBlindIndexData.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await Secret.deleteMany({ await Secret.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await SecretVersion.deleteMany({ await SecretVersion.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await SecretSnapshot.deleteMany({ await SecretSnapshot.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await SecretImport.deleteMany({ await SecretImport.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await Folder.deleteMany({ await Folder.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await FolderVersion.deleteMany({ await FolderVersion.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await Webhook.deleteMany({ await Webhook.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await TrustedIP.deleteMany({ await TrustedIP.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await Tag.deleteMany({ await Tag.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await IntegrationAuth.deleteMany({ await IntegrationAuth.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await Integration.deleteMany({ await Integration.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await ServiceToken.deleteMany({ await ServiceToken.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await ServiceTokenData.deleteMany({ await ServiceTokenData.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await ServiceTokenDataV3.deleteMany({ await ServiceTokenDataV3.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await ServiceTokenDataV3Key.deleteMany({ await ServiceTokenDataV3Key.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await AuditLog.deleteMany({ await AuditLog.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await Log.deleteMany({ await Log.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await Action.deleteMany({ await Action.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await SecretApprovalPolicy.deleteMany({ await SecretApprovalPolicy.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
await SecretApprovalRequest.deleteMany({ await SecretApprovalRequest.deleteMany({
workspace: { workspace: {
$in: workspaceIds $in: workspaceIds
} }
}, {
session
}); });
if (organization.customerId) {
// delete from stripe here
await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}`
);
}
return organization; return organization;
} catch (err) { } catch (err) {
await session.abortTransaction(); if (!existingSession) {
await session.abortTransaction();
}
throw InternalServerError({ throw InternalServerError({
message: "Failed to delete organization" message: "Failed to delete organization"
}); });
} finally { } finally {
session.endSession(); if (!existingSession) {
await session.commitTransaction();
session.endSession();
}
} }
} }
+32 -19
View File
@@ -57,10 +57,10 @@ import { getAnImportedSecret } from "../services/SecretImportService";
/** /**
* Validate scope for service token v3 * Validate scope for service token v3
* @param authPayload * @param authPayload
* @param environment * @param environment
* @param secretPath * @param secretPath
* @returns * @returns
*/ */
export const isValidScopeV3 = ({ export const isValidScopeV3 = ({
authPayload, authPayload,
@@ -68,37 +68,40 @@ export const isValidScopeV3 = ({
secretPath, secretPath,
requiredPermissions requiredPermissions
}: { }: {
authPayload: IServiceTokenDataV3, authPayload: IServiceTokenDataV3;
environment: string, environment: string;
secretPath: string, secretPath: string;
requiredPermissions: Permission[] requiredPermissions: Permission[];
}) => { }) => {
const { scopes } = authPayload; const { scopes } = authPayload;
const validScope = scopes.find( const validScope = scopes.find(
(scope) => (scope) =>
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) && picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment scope.environment === environment
); );
if (validScope && !requiredPermissions.every(permission => validScope.permissions.includes(permission))) { if (
validScope &&
!requiredPermissions.every((permission) => validScope.permissions.includes(permission))
) {
return false; return false;
} }
return Boolean(validScope); return Boolean(validScope);
} };
/** /**
* Validate scope for service token v2 * Validate scope for service token v2
* @param authPayload * @param authPayload
* @param environment * @param environment
* @param secretPath * @param secretPath
* @returns * @returns
*/ */
export const isValidScope = ( export const isValidScope = (
authPayload: IServiceTokenData, authPayload: IServiceTokenData,
environment: string, environment: string,
secretPath: string, secretPath: string
) => { ) => {
const { scopes: tkScopes } = authPayload; const { scopes: tkScopes } = authPayload;
const validScope = tkScopes.find( const validScope = tkScopes.find(
@@ -1000,12 +1003,22 @@ export const deleteSecretHelper = async ({
environment, environment,
type, type,
authData, authData,
secretPath = "/" secretPath = "/",
// used for update corner case and blindIndex goes wrong way
secretId
}: DeleteSecretParams) => { }: DeleteSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({ let secretBlindIndex = await generateSecretBlindIndexHelper({
secretName, secretName,
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId)
}); });
if (secretId) {
const secret = await Secret.findOne({
workspace: workspaceId,
environment,
_id: secretId
}).select("secretBlindIndex");
if (secret && secret.secretBlindIndex) secretBlindIndex = secret.secretBlindIndex;
}
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath); const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
+48 -10
View File
@@ -1,4 +1,4 @@
import mongoose, { Types } from "mongoose"; import mongoose, { Types, mongo } from "mongoose";
import { import {
APIKeyData, APIKeyData,
BackupPrivateKey, BackupPrivateKey,
@@ -222,15 +222,26 @@ const checkDeleteUserConditions = async ({
* @returns {User} user - deleted user * @returns {User} user - deleted user
*/ */
export const deleteUser = async ({ export const deleteUser = async ({
userId userId,
existingSession
}: { }: {
userId: Types.ObjectId userId: Types.ObjectId;
existingSession?: mongo.ClientSession;
}) => { }) => {
const session = await mongoose.startSession();
session.startTransaction(); let session;
if (existingSession) {
session = existingSession;
} else {
session = await mongoose.startSession();
session.startTransaction();
}
try { try {
const user = await User.findByIdAndDelete(userId); const user = await User.findByIdAndDelete(userId, {
session
});
if (!user) throw ResourceNotFoundError(); if (!user) throw ResourceNotFoundError();
@@ -240,22 +251,32 @@ export const deleteUser = async ({
await UserAction.deleteMany({ await UserAction.deleteMany({
user: user._id user: user._id
}, {
session
}); });
await BackupPrivateKey.deleteMany({ await BackupPrivateKey.deleteMany({
user: user._id user: user._id
}, {
session
}); });
await APIKeyData.deleteMany({ await APIKeyData.deleteMany({
user: user._id user: user._id
}, {
session
}); });
await Action.deleteMany({ await Action.deleteMany({
user: user._id user: user._id
}, {
session
}); });
await Log.deleteMany({ await Log.deleteMany({
user: user._id user: user._id
}, {
session
}); });
await TokenVersion.deleteMany({ await TokenVersion.deleteMany({
@@ -264,10 +285,14 @@ export const deleteUser = async ({
await Key.deleteMany({ await Key.deleteMany({
receiver: user._id receiver: user._id
}, {
session
}); });
const membershipOrgs = await MembershipOrg.find({ const membershipOrgs = await MembershipOrg.find({
user: userId user: userId
}, null, {
session
}); });
// delete organizations where user is only member // delete organizations where user is only member
@@ -280,13 +305,16 @@ export const deleteUser = async ({
// organization only has 1 member (the current user) // organization only has 1 member (the current user)
await deleteOrganization({ await deleteOrganization({
organizationId: membershipOrg.organization organizationId: membershipOrg.organization,
existingSession: session
}); });
} }
} }
const memberships = await Membership.find({ const memberships = await Membership.find({
user: userId user: userId
}, null, {
session
}); });
// delete workspaces where user is only member // delete workspaces where user is only member
@@ -299,26 +327,36 @@ export const deleteUser = async ({
// workspace only has 1 member (the current user) -> delete workspace // workspace only has 1 member (the current user) -> delete workspace
await deleteWorkspace({ await deleteWorkspace({
workspaceId: membership.workspace workspaceId: membership.workspace,
existingSession: session
}); });
} }
} }
await MembershipOrg.deleteMany({ await MembershipOrg.deleteMany({
user: userId user: userId
}, {
session
}); });
await Membership.deleteMany({ await Membership.deleteMany({
user: userId user: userId
}, {
session
}); });
return user; return user;
} catch (err) { } catch (err) {
await session.abortTransaction(); if (!existingSession) {
await session.abortTransaction();
}
throw InternalServerError({ throw InternalServerError({
message: "Failed to delete account" message: "Failed to delete account"
}) })
} finally { } finally {
session.endSession(); if (!existingSession) {
await session.commitTransaction();
session.endSession();
}
} }
} }
+71 -7
View File
@@ -1,4 +1,4 @@
import mongoose, { Types } from "mongoose"; import mongoose, { Types, mongo } from "mongoose";
import { import {
Bot, Bot,
BotKey, BotKey,
@@ -102,125 +102,189 @@ export const createWorkspace = async ({
* @param {String} obj.id - id of workspace to delete * @param {String} obj.id - id of workspace to delete
*/ */
export const deleteWorkspace = async ({ export const deleteWorkspace = async ({
workspaceId workspaceId,
existingSession
}: { }: {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
existingSession?: mongo.ClientSession;
}) => { }) => {
const session = await mongoose.startSession();
session.startTransaction(); let session;
if (existingSession) {
session = existingSession;
} else {
session = await mongoose.startSession();
session.startTransaction();
}
try { try {
const workspace = await Workspace.findByIdAndDelete(workspaceId); const workspace = await Workspace.findByIdAndDelete(workspaceId, { session });
if (!workspace) throw ResourceNotFoundError(); if (!workspace) throw ResourceNotFoundError();
await Membership.deleteMany({ await Membership.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await Key.deleteMany({ await Key.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await Bot.deleteMany({ await Bot.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await BotKey.deleteMany({ await BotKey.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await SecretBlindIndexData.deleteMany({ await SecretBlindIndexData.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await Secret.deleteMany({ await Secret.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await SecretVersion.deleteMany({ await SecretVersion.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await SecretSnapshot.deleteMany({ await SecretSnapshot.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await SecretImport.deleteMany({ await SecretImport.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await Folder.deleteMany({ await Folder.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await FolderVersion.deleteMany({ await FolderVersion.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await Webhook.deleteMany({ await Webhook.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await TrustedIP.deleteMany({ await TrustedIP.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await Tag.deleteMany({ await Tag.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await IntegrationAuth.deleteMany({ await IntegrationAuth.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await Integration.deleteMany({ await Integration.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await ServiceToken.deleteMany({ await ServiceToken.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await ServiceTokenData.deleteMany({ await ServiceTokenData.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await ServiceTokenDataV3.deleteMany({ await ServiceTokenDataV3.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await ServiceTokenDataV3Key.deleteMany({ await ServiceTokenDataV3Key.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await AuditLog.deleteMany({ await AuditLog.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await Log.deleteMany({ await Log.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await Action.deleteMany({ await Action.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await SecretApprovalPolicy.deleteMany({ await SecretApprovalPolicy.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
await SecretApprovalRequest.deleteMany({ await SecretApprovalRequest.deleteMany({
workspace: workspace._id workspace: workspace._id
}, {
session
}); });
return workspace; return workspace;
} catch (err) { } catch (err) {
await session.abortTransaction(); if (!existingSession) {
await session.abortTransaction();
}
throw InternalServerError({ throw InternalServerError({
message: "Failed to delete organization" message: "Failed to delete organization"
}); });
} finally { } finally {
session.endSession(); if (!existingSession) {
await session.commitTransaction();
session.endSession();
}
} }
}; };
@@ -64,6 +64,7 @@ export interface UpdateSecretParams {
export interface DeleteSecretParams { export interface DeleteSecretParams {
secretName: string; secretName: string;
secretId?: string;
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
environment: string; environment: string;
type: "shared" | "personal"; type: "shared" | "personal";
+22 -15
View File
@@ -5,7 +5,7 @@ import path from "path";
type TAppendFolderDTO = { type TAppendFolderDTO = {
folderName: string; folderName: string;
parentFolderId?: string; directory: string;
}; };
type TRenameFolderDTO = { type TRenameFolderDTO = {
@@ -50,9 +50,8 @@ export const folderBfsTraversal = async (
// bfs and then append to the folder // bfs and then append to the folder
const appendChild = (folders: TFolderSchema, folderName: string) => { const appendChild = (folders: TFolderSchema, folderName: string) => {
const folder = folders.children.find(({ name }) => name === folderName); const folder = folders.children.find(({ name }) => name === folderName);
if (folder) { if (folder) return { folder, hasCreated: false };
throw new Error("Folder already exists");
}
const id = generateFolderId(); const id = generateFolderId();
folders.version += 1; folders.version += 1;
folders.children.push({ folders.children.push({
@@ -61,24 +60,32 @@ const appendChild = (folders: TFolderSchema, folderName: string) => {
children: [], children: [],
version: 1 version: 1
}); });
return { id, name: folderName }; // last element that is the new one
return { folder: folders.children[folders.children.length - 1], hasCreated: true };
}; };
// root of append child wrapper // root of append child wrapper
export const appendFolder = ( export const appendFolder = (
folders: TFolderSchema, folders: TFolderSchema,
{ folderName, parentFolderId }: TAppendFolderDTO { folderName, directory }: TAppendFolderDTO
) => { ): { parent: TFolderSchema; child: TFolderSchema; hasCreated?: boolean } => {
const isRoot = !parentFolderId; if (directory === "/") {
const newFolder = appendChild(folders, folderName);
return { parent: folders, child: newFolder.folder, hasCreated: newFolder.hasCreated };
}
if (isRoot) { const segments = directory.split("/").filter(Boolean);
return appendChild(folders, folderName); const segment = segments.shift();
if (segment) {
const nestedFolders = appendChild(folders, segment);
return appendFolder(nestedFolders.folder, {
folderName,
directory: path.join("/", ...segments)
});
} }
const folder = searchByFolderId(folders, parentFolderId);
if (!folder) { const newFolder = appendChild(folders, folderName);
throw new Error("Parent Folder not found"); return { parent: folders, child: newFolder.folder, hasCreated: newFolder.hasCreated };
}
return appendChild(folder, folderName);
}; };
export const renameFolder = ( export const renameFolder = (
+2 -1
View File
@@ -379,7 +379,8 @@ export const DeleteSecretByNameV3 = z.object({
workspaceId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]), type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
secretPath: z.string().trim().default("/") secretPath: z.string().trim().default("/"),
secretId: z.string().trim().optional()
}), }),
params: z.object({ params: z.object({
secretName: z.string() secretName: z.string()
+10
View File
@@ -89,3 +89,13 @@ Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Azure. Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Azure.
![Azure SAML assignment](../../../images/sso/azure/enable-saml.png) ![Azure SAML assignment](../../../images/sso/azure/enable-saml.png)
<Note>
If you're configuring SAML SSO on a self-hosted instance of Infisical, make sure to
set the `JWT_PROVIDER_AUTH_SECRET` and `SITE_URL` environment variable for it to work:
- `JWT_PROVIDER_AUTH_SECRET`: This is secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
</Note>
+17 -2
View File
@@ -29,9 +29,24 @@ Obtain the **Client ID** and generate a new **Client Secret** for your GitHub OA
![GCP obtain OAuth2 credentials](../../../images/sso/github/credentials.png) ![GCP obtain OAuth2 credentials](../../../images/sso/github/credentials.png)
Back in your Infisical instance, add two new environment variables for the credentials of your GitHub OAuth application: Back in your Infisical instance, make sure to set the following environment variables:
- `CLIENT_ID_GITHUB_LOGIN`: The **Client ID** of your GitHub OAuth application. - `CLIENT_ID_GITHUB_LOGIN`: The **Client ID** of your GitHub OAuth application.
- `CLIENT_SECRET_GITHUB_LOGIN`: The **Client Secret** of your GitHub OAuth application. - `CLIENT_SECRET_GITHUB_LOGIN`: The **Client Secret** of your GitHub OAuth application.
- `JWT_PROVIDER_AUTH_SECRET`: A secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
Once added, restart your Infisical instance and log in with GitHub. Once added, restart your Infisical instance and log in with GitHub.
## FAQ
<AccordionGroup>
<Accordion title="Why is GitHub SSO not working?">
It is likely that you have misconfigured your self-hosted instance of Infisical. You should:
- Check that you have set the `CLIENT_ID_GITHUB_LOGIN`, `CLIENT_SECRET_GITHUB_LOGIN`,
`JWT_PROVIDER_AUTH_SECRET`, and `SITE_URL` environment variables.
- Check that the **Authorization callback URL** specified in GitHub matches the `SITE_URL` environment variable.
For example, if the former is `https://app.infisical.com/api/v1/sso/github` then the latter should be `https://app.infisical.com`.
</Accordion>
</AccordionGroup>
+17 -2
View File
@@ -28,10 +28,25 @@ Obtain the **Application ID** and **Secret** for your GitLab application.
![sso gitlab config](/images/sso/gitlab/credentials.png) ![sso gitlab config](/images/sso/gitlab/credentials.png)
Back in your Infisical instance, add 2-3 new environment variables for the credentials of your GitLab application: Back in your Infisical instance, make sure to set the following environment variables:
- `CLIENT_ID_GITLAB_LOGIN`: The **Client ID** of your GitLab application. - `CLIENT_ID_GITLAB_LOGIN`: The **Client ID** of your GitLab application.
- `CLIENT_SECRET_GITLAB_LOGIN`: The **Secret** of your GitLab application. - `CLIENT_SECRET_GITLAB_LOGIN`: The **Secret** of your GitLab application.
- (optional) `URL_GITLAB_LOGIN`: The URL of your self-hosted instance of GitLab where the OAuth application is registered. If no URL is passed in, this will default to `https://gitlab.com`. - (optional) `URL_GITLAB_LOGIN`: The URL of your self-hosted instance of GitLab where the OAuth application is registered. If no URL is passed in, this will default to `https://gitlab.com`.
- `JWT_PROVIDER_AUTH_SECRET`: A secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
Once added, restart your Infisical instance and log in with GitLab. Once added, restart your Infisical instance and log in with GitLab.
## FAQ
<AccordionGroup>
<Accordion title="Why is GitLab SSO not working?">
It is likely that you have misconfigured your self-hosted instance of Infisical. You should:
- Check that you have set the `CLIENT_ID_GITLAB_LOGIN`, `CLIENT_SECRET_GITLAB_LOGIN`,
`JWT_PROVIDER_AUTH_SECRET`, and `SITE_URL` environment variables.
- Check that the **Redirect URI** specified in GitLab matches the `SITE_URL` environment variable.
For example, if the former is `https://app.infisical.com/api/v1/sso/gitlab` then the latter should be `https://app.infisical.com`.
</Accordion>
</AccordionGroup>
+17 -2
View File
@@ -22,9 +22,24 @@ Obtain the **Client ID** and **Client Secret** for your GCP OAuth2 application.
![GCP obtain OAuth2 credentials](../../../images/sso/google/credentials.png) ![GCP obtain OAuth2 credentials](../../../images/sso/google/credentials.png)
Back in your Infisical instance, add two new environment variables for the credentials of your GCP OAuth2 application: Back in your Infisical instance, make sure to set the following environment variables:
- `CLIENT_ID_GOOGLE_LOGIN`: The **Client ID** of your GCP OAuth2 application. - `CLIENT_ID_GOOGLE_LOGIN`: The **Client ID** of your GCP OAuth2 application.
- `CLIENT_SECRET_GOOGLE_LOGIN`: The **Client Secret** of your GCP OAuth2 application. - `CLIENT_SECRET_GOOGLE_LOGIN`: The **Client Secret** of your GCP OAuth2 application.
- `JWT_PROVIDER_AUTH_SECRET`: A secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
Once added, restart your Infisical instance and log in with Google Once added, restart your Infisical instance and log in with Google
## FAQ
<AccordionGroup>
<Accordion title="Why is Google SSO not working?">
It is likely that you have misconfigured your self-hosted instance of Infisical. You should:
- Check that you have set the `CLIENT_ID_GOOGLE_LOGIN`, `CLIENT_SECRET_GOOGLE_LOGIN`,
`JWT_PROVIDER_AUTH_SECRET`, and `SITE_URL` environment variables.
- Check that the **Authorized redirect URI** specified in GCP matches the `SITE_URL` environment variable.
For example, if the former is `https://app.infisical.com/api/v1/sso/google` then the latter should be `https://app.infisical.com`.
</Accordion>
</AccordionGroup>
@@ -72,3 +72,11 @@ Back in JumpCloud, navigate to the **User Groups** tab and assign users to the n
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via JumpCloud. Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via JumpCloud.
![JumpCloud SAML assignment](../../../images/sso/jumpcloud/enable-saml.png) ![JumpCloud SAML assignment](../../../images/sso/jumpcloud/enable-saml.png)
<Note>
If you're configuring SAML SSO on a self-hosted instance of Infisical, make sure to
set the `JWT_PROVIDER_AUTH_SECRET` and `SITE_URL` environment variable for it to work:
- `JWT_PROVIDER_AUTH_SECRET`: This is secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
</Note>
+8
View File
@@ -77,3 +77,11 @@ At this point, you have configured everything you need within the context of the
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Okta. Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Okta.
![SAML Okta assignment](../../../images/sso/okta/enable-saml.png) ![SAML Okta assignment](../../../images/sso/okta/enable-saml.png)
<Note>
If you're configuring SAML SSO on a self-hosted instance of Infisical, make sure to
set the `JWT_PROVIDER_AUTH_SECRET` and `SITE_URL` environment variable for it to work:
- `JWT_PROVIDER_AUTH_SECRET`: This is secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
</Note>
+1 -1
View File
@@ -37,7 +37,7 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
) => ( ) => (
<DialogPrimitive.Portal> <DialogPrimitive.Portal>
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
className="fixed inset-0 z-[70] h-full w-full" className="fixed inset-0 z-20 h-full w-full"
style={{ backgroundColor: "rgba(0, 0, 0, 0.7)" }} style={{ backgroundColor: "rgba(0, 0, 0, 0.7)" }}
/> />
<DialogPrimitive.Content <DialogPrimitive.Content
+10 -2
View File
@@ -189,12 +189,20 @@ export const useDeleteSecretV3 = ({
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<{}, {}, TDeleteSecretsV3DTO>({ return useMutation<{}, {}, TDeleteSecretsV3DTO>({
mutationFn: async ({ secretPath = "/", type, environment, workspaceId, secretName }) => { mutationFn: async ({
secretPath = "/",
type,
environment,
workspaceId,
secretName,
secretId
}) => {
const reqBody = { const reqBody = {
workspaceId, workspaceId,
environment, environment,
type, type,
secretPath secretPath,
secretId
}; };
const { data } = await apiRequest.delete(`/api/v3/secrets/${secretName}`, { const { data } = await apiRequest.delete(`/api/v3/secrets/${secretName}`, {
+1
View File
@@ -120,6 +120,7 @@ export type TDeleteSecretsV3DTO = {
type: "shared" | "personal"; type: "shared" | "personal";
secretPath: string; secretPath: string;
secretName: string; secretName: string;
secretId?: string;
}; };
export type TCreateSecretBatchDTO = { export type TCreateSecretBatchDTO = {
+11
View File
@@ -51,6 +51,17 @@ export const useDeleteUser = () => {
return user; return user;
}, },
onSuccess: () => { onSuccess: () => {
localStorage.removeItem("protectedKey");
localStorage.removeItem("protectedKeyIV");
localStorage.removeItem("protectedKeyTag");
localStorage.removeItem("publicKey");
localStorage.removeItem("encryptedPrivateKey");
localStorage.removeItem("iv");
localStorage.removeItem("tag");
localStorage.removeItem("PRIVATE_KEY");
localStorage.removeItem("orgData.id");
localStorage.removeItem("projectData.id");
queryClient.clear(); queryClient.clear();
} }
}); });
@@ -34,7 +34,6 @@ import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck";
import { tempLocalStorage } from "@app/components/utilities/checks/tempLocalStorage"; import { tempLocalStorage } from "@app/components/utilities/checks/tempLocalStorage";
import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto"; import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto";
import { import {
@@ -217,7 +216,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
// } // }
}; };
putUserInOrg(); putUserInOrg();
onboardingCheck({});
}, [router.query.id]); }, [router.query.id]);
const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => { const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => {
+1
View File
@@ -12,6 +12,7 @@ export const navigateUserToOrg = async (router: NextRouter) => {
router.push(`/org/${userOrg}/overview`); router.push(`/org/${userOrg}/overview`);
} else { } else {
// user is not part of any org // user is not part of any org
localStorage.removeItem("orgData.id");
router.push("/org/none"); router.push("/org/none");
} }
} }
+36 -1
View File
@@ -14,7 +14,42 @@ const schema = yup
export type FormData = yup.InferType<typeof schema>; export type FormData = yup.InferType<typeof schema>;
export const NonePage = () => { export const NonePage = () => {
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["createOrg"] as const); 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"
});
useEffect(() => { useEffect(() => {
handlePopUpOpen("createOrg"); handlePopUpOpen("createOrg");
@@ -124,13 +124,15 @@ export const SecretListView = ({
comment, comment,
tags, tags,
skipMultilineEncoding, skipMultilineEncoding,
newKey newKey,
secretId
}: Partial<{ }: Partial<{
value: string; value: string;
comment: string; comment: string;
tags: string[]; tags: string[];
skipMultilineEncoding: boolean; skipMultilineEncoding: boolean;
newKey: string; newKey: string;
secretId: string;
}> = {} }> = {}
) => { ) => {
if (operation === "delete") { if (operation === "delete") {
@@ -139,7 +141,8 @@ export const SecretListView = ({
workspaceId, workspaceId,
secretPath, secretPath,
secretName: key, secretName: key,
type type,
secretId
}); });
return; return;
} }
@@ -249,9 +252,9 @@ export const SecretListView = ({
); );
const handleSecretDelete = useCallback(async () => { const handleSecretDelete = useCallback(async () => {
const { key } = popUp.deleteSecret?.data as DecryptedSecret; const { key, _id: secretId } = popUp.deleteSecret?.data as DecryptedSecret;
try { try {
await handleSecretOperation("delete", "shared", key); await handleSecretOperation("delete", "shared", key, { secretId });
queryClient.invalidateQueries( queryClient.invalidateQueries(
secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
); );
@@ -31,6 +31,7 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { useOrganization, useWorkspace } from "@app/context"; import { useOrganization, useWorkspace } from "@app/context";
import { import {
useCreateFolder,
useCreateSecretV3, useCreateSecretV3,
useDeleteSecretV3, useDeleteSecretV3,
useGetFoldersByEnv, useGetFoldersByEnv,
@@ -104,9 +105,24 @@ export const SecretOverviewPage = () => {
const { mutateAsync: createSecretV3 } = useCreateSecretV3(); const { mutateAsync: createSecretV3 } = useCreateSecretV3();
const { mutateAsync: updateSecretV3 } = useUpdateSecretV3(); const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3(); const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3();
const { mutateAsync: createFolder } = useCreateFolder();
const handleSecretCreate = async (env: string, key: string, value: string) => { const handleSecretCreate = async (env: string, key: string, value: string) => {
try { 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) {
await createFolder({
workspaceId,
environment: env,
directory,
folderName
});
}
}
await createSecretV3({ await createSecretV3({
environment: env, environment: env,
workspaceId, workspaceId,
@@ -154,13 +170,14 @@ export const SecretOverviewPage = () => {
} }
}; };
const handleSecretDelete = async (env: string, key: string) => { const handleSecretDelete = async (env: string, key: string, secretId?: string) => {
try { try {
await deleteSecretV3({ await deleteSecretV3({
environment: env, environment: env,
workspaceId, workspaceId,
secretPath, secretPath,
secretName: key, secretName: key,
secretId,
type: "shared" type: "shared"
}); });
createNotification({ createNotification({
@@ -188,7 +205,20 @@ export const SecretOverviewPage = () => {
}); });
}; };
const handleExploreEnvClick = (slug: string) => { const handleExploreEnvClick = async (slug: string) => {
if (secretPath !== "/") {
const path = secretPath.split("/");
const directory = path.slice(0, -1).join("/");
const folderName = path.at(-1);
if (folderName && directory) {
await createFolder({
workspaceId,
environment: slug,
directory,
folderName
});
}
}
const query: Record<string, string> = { ...router.query, env: slug }; const query: Record<string, string> = { ...router.query, env: slug };
const envIndex = userAvailableEnvs.findIndex((el) => slug === el.slug); const envIndex = userAvailableEnvs.findIndex((el) => slug === el.slug);
if (envIndex !== -1) { if (envIndex !== -1) {
@@ -335,7 +365,14 @@ export const SecretOverviewPage = () => {
query: { id: workspaceId, env: userAvailableEnvs?.[0]?.slug } query: { id: workspaceId, env: userAvailableEnvs?.[0]?.slug }
}} }}
> >
<Button className="mt-4" variant="outline_bg" colorSchema="primary" size="md">Go to {userAvailableEnvs?.[0]?.name}</Button> <Button
className="mt-4"
variant="outline_bg"
colorSchema="primary"
size="md"
>
Go to {userAvailableEnvs?.[0]?.name}
</Button>
</Link> </Link>
</EmptyState> </EmptyState>
</Td> </Td>
@@ -12,13 +12,14 @@ import { useToggle } from "@app/hooks";
type Props = { type Props = {
defaultValue?: string | null; defaultValue?: string | null;
secretName: string; secretName: string;
secretId?: string;
isCreatable?: boolean; isCreatable?: boolean;
isVisible?: boolean; isVisible?: boolean;
environment: string; environment: string;
secretPath: string; secretPath: string;
onSecretCreate: (env: string, key: string, value: string) => Promise<void>; onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: (env: string, key: string, value: string) => Promise<void>; onSecretUpdate: (env: string, key: string, value: string) => Promise<void>;
onSecretDelete: (env: string, key: string) => Promise<void>; onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
}; };
export const SecretEditRow = ({ export const SecretEditRow = ({
@@ -30,7 +31,8 @@ export const SecretEditRow = ({
onSecretDelete, onSecretDelete,
environment, environment,
secretPath, secretPath,
isVisible isVisible,
secretId
}: Props) => { }: Props) => {
const { const {
handleSubmit, handleSubmit,
@@ -77,7 +79,7 @@ export const SecretEditRow = ({
const handleDeleteSecret = async () => { const handleDeleteSecret = async () => {
setIsDeleting.on(); setIsDeleting.on();
try { try {
await onSecretDelete(environment, secretName); await onSecretDelete(environment, secretName, secretId);
reset({ value: undefined }); reset({ value: undefined });
} finally { } finally {
setIsDeleting.off(); setIsDeleting.off();
@@ -24,7 +24,7 @@ type Props = {
getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined; getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined;
onSecretCreate: (env: string, key: string, value: string) => Promise<void>; onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: (env: string, key: string, value: string) => Promise<void>; onSecretUpdate: (env: string, key: string, value: string) => Promise<void>;
onSecretDelete: (env: string, key: string) => Promise<void>; onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
}; };
export const SecretOverviewTableRow = ({ export const SecretOverviewTableRow = ({
@@ -149,6 +149,7 @@ export const SecretOverviewTableRow = ({
isVisible={isSecretVisible} isVisible={isSecretVisible}
secretName={secretKey} secretName={secretKey}
defaultValue={secret?.value} defaultValue={secret?.value}
secretId={secret?._id}
isCreatable={isCreatable} isCreatable={isCreatable}
onSecretDelete={onSecretDelete} onSecretDelete={onSecretDelete}
onSecretCreate={onSecretCreate} onSecretCreate={onSecretCreate}
@@ -71,7 +71,7 @@ export const PreviewSection = () => {
console.error(err); console.error(err);
} }
}; };
return ( return (
<div> <div>
{subscription && {subscription &&
@@ -54,9 +54,7 @@ export const SecretTagsSection = (): JSX.Element => {
colorSchema="secondary" colorSchema="secondary"
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => { onClick={() => {
console.log("x");
handlePopUpOpen("CreateSecretTag"); handlePopUpOpen("CreateSecretTag");
console.log("x2");
}} }}
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >