mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 13:28:27 +00:00
Merge branch 'main' into feat/create-multiple-orgs-under-same-account
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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";
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,24 @@ Obtain the **Client ID** and generate a new **Client Secret** for your GitHub OA
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
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>
|
||||||
@@ -28,10 +28,25 @@ Obtain the **Application ID** and **Secret** for your GitLab application.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
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>
|
||||||
@@ -22,9 +22,24 @@ Obtain the **Client ID** and **Client Secret** for your GCP OAuth2 application.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
<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>
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
<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>
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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}`, {
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
+5
-3
@@ -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();
|
||||||
|
|||||||
+2
-1
@@ -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}
|
||||||
|
|||||||
+1
-1
@@ -71,7 +71,7 @@ export const PreviewSection = () => {
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{subscription &&
|
{subscription &&
|
||||||
|
|||||||
-2
@@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user