mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-02 16:55:02 +00:00
Compare commits
6 Commits
misc/add-d
...
folders
Author | SHA1 | Date | |
---|---|---|---|
1aa7c654f0 | |||
0c4cada63e | |||
b4703c2e67 | |||
75958d4d10 | |||
532c864a89 | |||
6c1115c5d3 |
@ -14,6 +14,7 @@ import * as stripeController from './stripeController';
|
|||||||
import * as userActionController from './userActionController';
|
import * as userActionController from './userActionController';
|
||||||
import * as userController from './userController';
|
import * as userController from './userController';
|
||||||
import * as workspaceController from './workspaceController';
|
import * as workspaceController from './workspaceController';
|
||||||
|
import * as secretsFolderController from './secretsFolderController'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
authController,
|
authController,
|
||||||
@ -31,5 +32,6 @@ export {
|
|||||||
stripeController,
|
stripeController,
|
||||||
userActionController,
|
userActionController,
|
||||||
userController,
|
userController,
|
||||||
workspaceController
|
workspaceController,
|
||||||
|
secretsFolderController
|
||||||
};
|
};
|
||||||
|
89
backend/src/controllers/v1/secretsFolderController.ts
Normal file
89
backend/src/controllers/v1/secretsFolderController.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { Secret } from '../../models';
|
||||||
|
import Folder from '../../models/folder';
|
||||||
|
import { BadRequestError } from '../../utils/errors';
|
||||||
|
import { ROOT_FOLDER_PATH, getFolderPath, getParentPath, normalizePath, validateFolderName } from '../../utils/folder';
|
||||||
|
import { ADMIN, MEMBER } from '../../variables';
|
||||||
|
import { validateMembership } from '../../helpers/membership';
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// verify workspace id/environment
|
||||||
|
export const createFolder = async (req: Request, res: Response) => {
|
||||||
|
const { workspaceId, environment, folderName, parentFolderId } = req.body
|
||||||
|
if (!validateFolderName(folderName)) {
|
||||||
|
throw BadRequestError({ message: "Folder name cannot contain spaces. Only underscore and dashes" })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentFolderId) {
|
||||||
|
const parentFolder = await Folder.find({ environment: environment, workspace: workspaceId, id: parentFolderId });
|
||||||
|
if (!parentFolder) {
|
||||||
|
throw BadRequestError({ message: "The parent folder doesn't exist" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let completePath = await getFolderPath(parentFolderId)
|
||||||
|
if (completePath == ROOT_FOLDER_PATH) {
|
||||||
|
completePath = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentFolderPath = completePath + "/" + folderName // construct new path with current folder to be created
|
||||||
|
const normalizedCurrentPath = normalizePath(currentFolderPath)
|
||||||
|
const normalizedParentPath = getParentPath(normalizedCurrentPath)
|
||||||
|
|
||||||
|
const existingFolder = await Folder.findOne({
|
||||||
|
name: folderName,
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment: environment,
|
||||||
|
parent: parentFolderId,
|
||||||
|
path: normalizedCurrentPath
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingFolder) {
|
||||||
|
return res.json(existingFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFolder = new Folder({
|
||||||
|
name: folderName,
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment: environment,
|
||||||
|
parent: parentFolderId,
|
||||||
|
path: normalizedCurrentPath,
|
||||||
|
parentPath: normalizedParentPath
|
||||||
|
});
|
||||||
|
|
||||||
|
await newFolder.save();
|
||||||
|
|
||||||
|
return res.json(newFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteFolder = async (req: Request, res: Response) => {
|
||||||
|
const { folderId } = req.params
|
||||||
|
const queue: any[] = [folderId];
|
||||||
|
|
||||||
|
const folder = await Folder.findById(folderId);
|
||||||
|
if (!folder) {
|
||||||
|
throw BadRequestError({ message: "The folder doesn't exist" })
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that user is a member of the workspace
|
||||||
|
await validateMembership({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: folder.workspace as any,
|
||||||
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
});
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const currentFolderId = queue.shift();
|
||||||
|
|
||||||
|
const childFolders = await Folder.find({ parent: currentFolderId });
|
||||||
|
for (const childFolder of childFolders) {
|
||||||
|
queue.push(childFolder._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Secret.deleteMany({ folder: currentFolderId });
|
||||||
|
|
||||||
|
await Folder.deleteOne({ _id: currentFolderId });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send()
|
||||||
|
}
|
@ -11,7 +11,7 @@ import {
|
|||||||
ACTION_UPDATE_SECRETS,
|
ACTION_UPDATE_SECRETS,
|
||||||
ACTION_DELETE_SECRETS
|
ACTION_DELETE_SECRETS
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import { UnauthorizedRequestError, ValidationError } from '../../utils/errors';
|
import { BadRequestError, UnauthorizedRequestError, ValidationError } from '../../utils/errors';
|
||||||
import { EventService } from '../../services';
|
import { EventService } from '../../services';
|
||||||
import { eventPushSecrets } from '../../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
import { EESecretService, EELogService } from '../../ee/services';
|
import { EESecretService, EELogService } from '../../ee/services';
|
||||||
@ -25,6 +25,8 @@ import {
|
|||||||
BatchSecretRequest,
|
BatchSecretRequest,
|
||||||
BatchSecret
|
BatchSecret
|
||||||
} from '../../types/secret';
|
} from '../../types/secret';
|
||||||
|
import { getFolderPath, getFoldersInDirectory, normalizePath } from '../../utils/folder';
|
||||||
|
import Folder from '../../models/folder';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Peform a batch of any specified CUD secret operations
|
* Peform a batch of any specified CUD secret operations
|
||||||
@ -50,11 +52,18 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
const deleteSecrets: Types.ObjectId[] = [];
|
const deleteSecrets: Types.ObjectId[] = [];
|
||||||
const actions: IAction[] = [];
|
const actions: IAction[] = [];
|
||||||
|
|
||||||
requests.forEach((request) => {
|
for (const request of requests) {
|
||||||
|
const folderId = request.secret.folder
|
||||||
|
|
||||||
|
// need to auth folder
|
||||||
|
const fullFolderPath = await getFolderPath(folderId)
|
||||||
|
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'POST':
|
case 'POST':
|
||||||
createSecrets.push({
|
createSecrets.push({
|
||||||
...request.secret,
|
...request.secret,
|
||||||
|
path: fullFolderPath,
|
||||||
|
folder: folderId,
|
||||||
version: 1,
|
version: 1,
|
||||||
user: request.secret.type === SECRET_PERSONAL ? req.user : undefined,
|
user: request.secret.type === SECRET_PERSONAL ? req.user : undefined,
|
||||||
environment,
|
environment,
|
||||||
@ -64,6 +73,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
case 'PATCH':
|
case 'PATCH':
|
||||||
updateSecrets.push({
|
updateSecrets.push({
|
||||||
...request.secret,
|
...request.secret,
|
||||||
|
folder: folderId,
|
||||||
|
path: fullFolderPath,
|
||||||
_id: new Types.ObjectId(request.secret._id)
|
_id: new Types.ObjectId(request.secret._id)
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@ -71,13 +82,14 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
deleteSecrets.push(new Types.ObjectId(request.secret._id));
|
deleteSecrets.push(new Types.ObjectId(request.secret._id));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// handle create secrets
|
// handle create secrets
|
||||||
let createdSecrets: ISecret[] = [];
|
let createdSecrets: ISecret[] = [];
|
||||||
if (createSecrets.length > 0) {
|
if (createSecrets.length > 0) {
|
||||||
createdSecrets = await Secret.insertMany(createSecrets);
|
createdSecrets = await Secret.insertMany(createSecrets);
|
||||||
// (EE) add secret versions for new secrets
|
// (EE) add secret versions for new secrets
|
||||||
|
|
||||||
await EESecretService.addSecretVersions({
|
await EESecretService.addSecretVersions({
|
||||||
secretVersions: createdSecrets.map((n: any) => {
|
secretVersions: createdSecrets.map((n: any) => {
|
||||||
return ({
|
return ({
|
||||||
@ -347,21 +359,10 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
listOfSecretsToCreate = [req.body.secrets];
|
listOfSecretsToCreate = [req.body.secrets];
|
||||||
}
|
}
|
||||||
|
|
||||||
type secretsToCreateType = {
|
|
||||||
type: string;
|
|
||||||
secretKeyCiphertext: string;
|
|
||||||
secretKeyIV: string;
|
|
||||||
secretKeyTag: string;
|
|
||||||
secretValueCiphertext: string;
|
|
||||||
secretValueIV: string;
|
|
||||||
secretValueTag: string;
|
|
||||||
secretCommentCiphertext: string;
|
|
||||||
secretCommentIV: string;
|
|
||||||
secretCommentTag: string;
|
|
||||||
tags: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const secretsToInsert: ISecret[] = listOfSecretsToCreate.map(({
|
const secretsToInsert: ISecret[] = [];
|
||||||
|
|
||||||
|
for (const {
|
||||||
type,
|
type,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
@ -372,9 +373,12 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
tags
|
tags,
|
||||||
}: secretsToCreateType) => {
|
folder
|
||||||
return ({
|
} of listOfSecretsToCreate) {
|
||||||
|
const fullFolderPath = await getFolderPath(folder)
|
||||||
|
|
||||||
|
const secret: any = {
|
||||||
version: 1,
|
version: 1,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
type,
|
type,
|
||||||
@ -389,9 +393,13 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
tags
|
tags,
|
||||||
});
|
folder: folder,
|
||||||
})
|
path: fullFolderPath
|
||||||
|
};
|
||||||
|
|
||||||
|
secretsToInsert.push(secret);
|
||||||
|
}
|
||||||
|
|
||||||
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map((insertedSecret) => insertedSecret.toObject());
|
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map((insertedSecret) => insertedSecret.toObject());
|
||||||
|
|
||||||
@ -535,10 +543,13 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = getPostHogClient();
|
||||||
|
|
||||||
const { workspaceId, environment, tagSlugs } = req.query;
|
const { workspaceId, environment, tagSlugs, secretsPath } = req.query;
|
||||||
|
|
||||||
|
const normalizedPath = normalizePath(secretsPath as string)
|
||||||
const tagNamesList = typeof tagSlugs === 'string' && tagSlugs !== '' ? tagSlugs.split(',') : [];
|
const tagNamesList = typeof tagSlugs === 'string' && tagSlugs !== '' ? tagSlugs.split(',') : [];
|
||||||
let userId = "" // used for getting personal secrets for user
|
let userId = "" // used for getting personal secrets for user
|
||||||
let userEmail = "" // used for posthog
|
let userEmail = "" // used for posthog
|
||||||
|
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
userId = req.user._id;
|
userId = req.user._id;
|
||||||
userEmail = req.user.email;
|
userEmail = req.user.email;
|
||||||
@ -558,6 +569,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let secrets: any
|
let secrets: any
|
||||||
let secretQuery: any
|
let secretQuery: any
|
||||||
|
|
||||||
@ -591,6 +603,9 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add path to secrets query
|
||||||
|
secretQuery.path = normalizedPath
|
||||||
|
|
||||||
if (hasWriteOnlyAccess) {
|
if (hasWriteOnlyAccess) {
|
||||||
secrets = await Secret.find(secretQuery).select("secretKeyCiphertext secretKeyIV secretKeyTag")
|
secrets = await Secret.find(secretQuery).select("secretKeyCiphertext secretKeyIV secretKeyTag")
|
||||||
} else {
|
} else {
|
||||||
@ -614,6 +629,8 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
ipAddress: req.ip
|
ipAddress: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const folders = await getFoldersInDirectory(workspaceId as string, environment as string, normalizedPath)
|
||||||
|
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: 'secrets pulled',
|
event: 'secrets pulled',
|
||||||
@ -629,11 +646,11 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets
|
secrets,
|
||||||
|
folders
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getOnlySecretKeys = async (req: Request, res: Response) => {
|
export const getOnlySecretKeys = async (req: Request, res: Response) => {
|
||||||
const { workspaceId, environment } = req.query;
|
const { workspaceId, environment } = req.query;
|
||||||
|
|
||||||
@ -754,7 +771,9 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
tags: string[]
|
tags: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateOperationsToPerform = req.body.secrets.map((secret: PatchSecret) => {
|
const updateOperationsToPerform = [];
|
||||||
|
|
||||||
|
for (const secret of req.body.secrets) {
|
||||||
const {
|
const {
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
@ -765,10 +784,13 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
tags
|
tags,
|
||||||
|
folder
|
||||||
} = secret;
|
} = secret;
|
||||||
|
|
||||||
return ({
|
const fullFolderPath = await getFolderPath(folder)
|
||||||
|
|
||||||
|
const updateOperation = {
|
||||||
updateOne: {
|
updateOne: {
|
||||||
filter: { _id: new Types.ObjectId(secret.id) },
|
filter: { _id: new Types.ObjectId(secret.id) },
|
||||||
update: {
|
update: {
|
||||||
@ -782,6 +804,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
tags,
|
tags,
|
||||||
|
path: fullFolderPath,
|
||||||
|
folder: folder,
|
||||||
...((
|
...((
|
||||||
secretCommentCiphertext !== undefined &&
|
secretCommentCiphertext !== undefined &&
|
||||||
secretCommentIV &&
|
secretCommentIV &&
|
||||||
@ -793,8 +817,10 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
} : {}),
|
} : {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
});
|
|
||||||
|
updateOperationsToPerform.push(updateOperation);
|
||||||
|
}
|
||||||
|
|
||||||
await Secret.bulkWrite(updateOperationsToPerform);
|
await Secret.bulkWrite(updateOperationsToPerform);
|
||||||
|
|
||||||
|
@ -45,7 +45,8 @@ import {
|
|||||||
password as v1PasswordRouter,
|
password as v1PasswordRouter,
|
||||||
stripe as v1StripeRouter,
|
stripe as v1StripeRouter,
|
||||||
integration as v1IntegrationRouter,
|
integration as v1IntegrationRouter,
|
||||||
integrationAuth as v1IntegrationAuthRouter
|
integrationAuth as v1IntegrationAuthRouter,
|
||||||
|
secretsFolder as v1SecretsFolder
|
||||||
} from './routes/v1';
|
} from './routes/v1';
|
||||||
import {
|
import {
|
||||||
signup as v2SignupRouter,
|
signup as v2SignupRouter,
|
||||||
@ -138,6 +139,7 @@ const main = async () => {
|
|||||||
app.use('/api/v1/stripe', v1StripeRouter);
|
app.use('/api/v1/stripe', v1StripeRouter);
|
||||||
app.use('/api/v1/integration', v1IntegrationRouter);
|
app.use('/api/v1/integration', v1IntegrationRouter);
|
||||||
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
||||||
|
app.use('/api/v1/folder', v1SecretsFolder)
|
||||||
|
|
||||||
// v2 routes
|
// v2 routes
|
||||||
app.use('/api/v2/signup', v2SignupRouter);
|
app.use('/api/v2/signup', v2SignupRouter);
|
||||||
|
36
backend/src/models/folder.ts
Normal file
36
backend/src/models/folder.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Schema, Types, model } from 'mongoose';
|
||||||
|
|
||||||
|
const folderSchema = new Schema({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Workspace',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Folder',
|
||||||
|
required: false, // optional for root folders
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
parentPath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
timestamps: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const Folder = model('Folder', folderSchema);
|
||||||
|
|
||||||
|
export default Folder;
|
@ -24,6 +24,8 @@ export interface ISecret {
|
|||||||
secretCommentTag?: string;
|
secretCommentTag?: string;
|
||||||
secretCommentHash?: string;
|
secretCommentHash?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
|
path?: string,
|
||||||
|
folder?: Types.ObjectId
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretSchema = new Schema<ISecret>(
|
const secretSchema = new Schema<ISecret>(
|
||||||
@ -53,6 +55,17 @@ const secretSchema = new Schema<ISecret>(
|
|||||||
type: [Schema.Types.ObjectId],
|
type: [Schema.Types.ObjectId],
|
||||||
default: []
|
default: []
|
||||||
},
|
},
|
||||||
|
// the full path to the secret in relation to folders
|
||||||
|
path: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: "/"
|
||||||
|
},
|
||||||
|
folder: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Folder',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
environment: {
|
environment: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
@ -15,6 +15,7 @@ import password from './password';
|
|||||||
import stripe from './stripe';
|
import stripe from './stripe';
|
||||||
import integration from './integration';
|
import integration from './integration';
|
||||||
import integrationAuth from './integrationAuth';
|
import integrationAuth from './integrationAuth';
|
||||||
|
import secretsFolder from './secretsFolder'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
signup,
|
signup,
|
||||||
@ -33,5 +34,6 @@ export {
|
|||||||
password,
|
password,
|
||||||
stripe,
|
stripe,
|
||||||
integration,
|
integration,
|
||||||
integrationAuth
|
integrationAuth,
|
||||||
|
secretsFolder
|
||||||
};
|
};
|
||||||
|
40
backend/src/routes/v1/secretsFolder.ts
Normal file
40
backend/src/routes/v1/secretsFolder.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth,
|
||||||
|
validateRequest
|
||||||
|
} from '../../middleware';
|
||||||
|
import { body, param } from 'express-validator';
|
||||||
|
import { createFolder, deleteFolder } from '../../controllers/v1/secretsFolderController';
|
||||||
|
import { ADMIN, MEMBER } from '../../variables';
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
location: 'body'
|
||||||
|
}),
|
||||||
|
body('workspaceId').exists(),
|
||||||
|
body('environment').exists(),
|
||||||
|
body('folderName').exists(),
|
||||||
|
body('parentFolderId'),
|
||||||
|
validateRequest,
|
||||||
|
createFolder
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/:folderId',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
param('folderId').exists(),
|
||||||
|
validateRequest,
|
||||||
|
deleteFolder
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export default router;
|
87
backend/src/utils/folder.ts
Normal file
87
backend/src/utils/folder.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import Folder from "../models/folder";
|
||||||
|
|
||||||
|
export const ROOT_FOLDER_PATH = "/"
|
||||||
|
|
||||||
|
export const getFolderPath = async (folderId: string) => {
|
||||||
|
let currentFolder = await Folder.findById(folderId);
|
||||||
|
const pathSegments = [];
|
||||||
|
|
||||||
|
while (currentFolder) {
|
||||||
|
pathSegments.unshift(currentFolder.name);
|
||||||
|
currentFolder = currentFolder.parent ? await Folder.findById(currentFolder.parent) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/' + pathSegments.join('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the folder ID associated with the specified secret path in the given workspace and environment.
|
||||||
|
@param workspaceId - The ID of the workspace to search in.
|
||||||
|
@param environment - The environment to search in.
|
||||||
|
@param secretPath - The secret path to search for.
|
||||||
|
@returns The folder ID associated with the specified secret path, or undefined if the path is at the root folder level.
|
||||||
|
@throws Error if the specified secret path is not found.
|
||||||
|
*/
|
||||||
|
export const getFolderIdFromPath = async (workspaceId: string, environment: string, secretPath: string) => {
|
||||||
|
const secretPathParts = secretPath.split("/").filter(path => path != "")
|
||||||
|
if (secretPathParts.length <= 1) {
|
||||||
|
return undefined // root folder, so no folder id
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderId = await Folder.find({ path: secretPath, workspace: workspaceId, environment: environment })
|
||||||
|
if (!folderId) {
|
||||||
|
throw Error("Secret path not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderId
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up a path by removing empty parts, duplicate slashes,
|
||||||
|
* and ensuring it starts with ROOT_FOLDER_PATH.
|
||||||
|
* @param path - The input path to clean up.
|
||||||
|
* @returns The cleaned-up path string.
|
||||||
|
*/
|
||||||
|
export const normalizePath = (path: string) => {
|
||||||
|
if (path == undefined || path == "" || path == ROOT_FOLDER_PATH) {
|
||||||
|
return ROOT_FOLDER_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathParts = path.split("/").filter(part => part != "")
|
||||||
|
const cleanPathString = ROOT_FOLDER_PATH + pathParts.join("/")
|
||||||
|
|
||||||
|
return cleanPathString
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getFoldersInDirectory = async (workspaceId: string, environment: string, pathString: string) => {
|
||||||
|
const normalizedPath = normalizePath(pathString)
|
||||||
|
const foldersInDirectory = await Folder.find({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment: environment,
|
||||||
|
parentPath: normalizedPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
return foldersInDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parent path of the given path.
|
||||||
|
* @param path - The input path.
|
||||||
|
* @returns The parent path string.
|
||||||
|
*/
|
||||||
|
export const getParentPath = (path: string) => {
|
||||||
|
const normalizedPath = normalizePath(path);
|
||||||
|
const folderParts = normalizedPath.split('/').filter(part => part !== '');
|
||||||
|
|
||||||
|
let folderParent = ROOT_FOLDER_PATH;
|
||||||
|
if (folderParts.length > 1) {
|
||||||
|
folderParent = ROOT_FOLDER_PATH + folderParts.slice(0, folderParts.length - 1).join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateFolderName = (folderName: string) => {
|
||||||
|
const validNameRegex = /^[a-zA-Z0-9-_]+$/;
|
||||||
|
return validNameRegex.test(folderName);
|
||||||
|
}
|
@ -115,6 +115,7 @@ func CallGetSecretsV2(httpClient *resty.Client, request GetEncryptedSecretsV2Req
|
|||||||
SetQueryParam("environment", request.Environment).
|
SetQueryParam("environment", request.Environment).
|
||||||
SetQueryParam("workspaceId", request.WorkspaceId).
|
SetQueryParam("workspaceId", request.WorkspaceId).
|
||||||
SetQueryParam("tagSlugs", request.TagSlugs).
|
SetQueryParam("tagSlugs", request.TagSlugs).
|
||||||
|
SetQueryParam("secretsPath", request.SecretPath).
|
||||||
Get(fmt.Sprintf("%v/v2/secrets", config.INFISICAL_URL))
|
Get(fmt.Sprintf("%v/v2/secrets", config.INFISICAL_URL))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -198,10 +198,21 @@ type GetEncryptedSecretsV2Request struct {
|
|||||||
Environment string `json:"environment"`
|
Environment string `json:"environment"`
|
||||||
WorkspaceId string `json:"workspaceId"`
|
WorkspaceId string `json:"workspaceId"`
|
||||||
TagSlugs string `json:"tagSlugs"`
|
TagSlugs string `json:"tagSlugs"`
|
||||||
|
SecretPath string `json:"secretPath"`
|
||||||
|
FolderId string `json:"folderId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetEncryptedSecretsV2Response struct {
|
type Folders struct {
|
||||||
Secrets []struct {
|
ID string `json:"_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Workspace string `json:"workspace"`
|
||||||
|
Environment string `json:"environment"`
|
||||||
|
Parent string `json:"parent"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Secrets struct {
|
||||||
ID string `json:"_id"`
|
ID string `json:"_id"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
Workspace string `json:"workspace"`
|
Workspace string `json:"workspace"`
|
||||||
@ -226,7 +237,12 @@ type GetEncryptedSecretsV2Response struct {
|
|||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
Workspace string `json:"workspace"`
|
Workspace string `json:"workspace"`
|
||||||
} `json:"tags"`
|
} `json:"tags"`
|
||||||
} `json:"secrets"`
|
}
|
||||||
|
|
||||||
|
type GetEncryptedSecretsV2Response struct {
|
||||||
|
Secrets []Secrets `json:"secrets"`
|
||||||
|
|
||||||
|
Folders []Folders `json:"folders"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetServiceTokenDetailsResponse struct {
|
type GetServiceTokenDetailsResponse struct {
|
||||||
|
@ -74,7 +74,12 @@ var exportCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, WorkspaceId: projectId})
|
secretsPath, err := cmd.Flags().GetString("path")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets, _, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, WorkspaceId: projectId, Path: secretsPath})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to fetch secrets")
|
util.HandleError(err, "Unable to fetch secrets")
|
||||||
}
|
}
|
||||||
@ -112,6 +117,7 @@ func init() {
|
|||||||
exportCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
exportCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||||
exportCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs")
|
exportCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs")
|
||||||
exportCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets from")
|
exportCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets from")
|
||||||
|
exportCmd.Flags().String("path", "/", "The path to the folder where secrets are located. Defaults to root folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format according to the format flag
|
// Format according to the format flag
|
||||||
|
@ -82,7 +82,12 @@ var runCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
|
secretsPath, err := cmd.Flags().GetString("path")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets, _, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, Path: secretsPath})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid")
|
util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid")
|
||||||
@ -182,6 +187,7 @@ func init() {
|
|||||||
runCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
runCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
||||||
runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")")
|
runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")")
|
||||||
runCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs ")
|
runCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs ")
|
||||||
|
runCmd.Flags().String("path", "/", "The path to the folder where secrets are located. Defaults to root folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Will execute a single command and pass in the given secrets into the process
|
// Will execute a single command and pass in the given secrets into the process
|
||||||
|
@ -44,6 +44,11 @@ var secretsCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secretsPath, err := cmd.Flags().GetString("path")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
shouldExpandSecrets, err := cmd.Flags().GetBool("expand")
|
shouldExpandSecrets, err := cmd.Flags().GetBool("expand")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err)
|
util.HandleError(err)
|
||||||
@ -54,7 +59,8 @@ var secretsCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
|
normalizedPath := util.NormalizePath(secretsPath)
|
||||||
|
secrets, folders, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, Path: normalizedPath})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err)
|
util.HandleError(err)
|
||||||
}
|
}
|
||||||
@ -63,6 +69,10 @@ var secretsCmd = &cobra.Command{
|
|||||||
secrets = util.SubstituteSecrets(secrets)
|
secrets = util.SubstituteSecrets(secrets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(folders) > 0 {
|
||||||
|
visualize.PrintSecretFolders(folders)
|
||||||
|
}
|
||||||
|
|
||||||
visualize.PrintAllSecretDetails(secrets)
|
visualize.PrintAllSecretDetails(secrets)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -142,7 +152,7 @@ var secretsSetCmd = &cobra.Command{
|
|||||||
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
|
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
|
||||||
|
|
||||||
// pull current secrets
|
// pull current secrets
|
||||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName})
|
secrets, _, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "unable to retrieve secrets")
|
util.HandleError(err, "unable to retrieve secrets")
|
||||||
}
|
}
|
||||||
@ -263,13 +273,14 @@ var secretsSetCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print secret operations
|
// Print secret operations
|
||||||
headers := [...]string{"SECRET NAME", "SECRET VALUE", "STATUS"}
|
secretHeaders := [...]string{"SECRET NAME", "SECRET VALUE", "STATUS"}
|
||||||
rows := [][3]string{}
|
secretRows := [][3]string{}
|
||||||
for _, secretOperation := range secretOperations {
|
for _, secretOperation := range secretOperations {
|
||||||
rows = append(rows, [...]string{secretOperation.SecretKey, secretOperation.SecretValue, secretOperation.SecretOperation})
|
secretRows = append(secretRows, [...]string{secretOperation.SecretKey, secretOperation.SecretValue, secretOperation.SecretOperation})
|
||||||
}
|
}
|
||||||
|
|
||||||
visualize.Table(headers, rows)
|
// visualize.PrintSecretFolders()
|
||||||
|
visualize.SecretsTable(secretHeaders, secretRows)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +310,7 @@ var secretsDeleteCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to get local project details")
|
util.HandleError(err, "Unable to get local project details")
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName})
|
secrets, _, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to fetch secrets")
|
util.HandleError(err, "Unable to fetch secrets")
|
||||||
}
|
}
|
||||||
@ -360,7 +371,7 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
|
secrets, _, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "To fetch all secrets")
|
util.HandleError(err, "To fetch all secrets")
|
||||||
}
|
}
|
||||||
@ -403,7 +414,7 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
|
secrets, _, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "To fetch all secrets")
|
util.HandleError(err, "To fetch all secrets")
|
||||||
}
|
}
|
||||||
@ -602,6 +613,7 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod
|
|||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||||
|
secretsCmd.Flags().String("path", "/", "The path to the folder where secrets are located. Defaults to root folder")
|
||||||
secretsCmd.AddCommand(secretsGenerateExampleEnvCmd)
|
secretsCmd.AddCommand(secretsGenerateExampleEnvCmd)
|
||||||
|
|
||||||
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||||
|
@ -56,4 +56,5 @@ type GetAllSecretsParameters struct {
|
|||||||
InfisicalToken string
|
InfisicalToken string
|
||||||
TagSlugs string
|
TagSlugs string
|
||||||
WorkspaceId string
|
WorkspaceId string
|
||||||
|
Path string
|
||||||
}
|
}
|
||||||
|
@ -137,3 +137,36 @@ func getCurrentBranch() (string, error) {
|
|||||||
}
|
}
|
||||||
return path.Base(strings.TrimSpace(out.String())), nil
|
return path.Base(strings.TrimSpace(out.String())), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSplitPathByDash(path string) []string {
|
||||||
|
pathParts := strings.Split(path, "/")
|
||||||
|
var filteredPathParts []string
|
||||||
|
for _, s := range pathParts {
|
||||||
|
if s != "" {
|
||||||
|
filteredPathParts = append(filteredPathParts, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredPathParts
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizePath cleans up a path by removing empty parts, duplicate slashes,
|
||||||
|
// and ensuring it starts with ROOT_FOLDER_PATH.
|
||||||
|
func NormalizePath(path string) string {
|
||||||
|
ROOT_FOLDER_PATH := "/"
|
||||||
|
|
||||||
|
if path == "" || path == ROOT_FOLDER_PATH {
|
||||||
|
return ROOT_FOLDER_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
pathParts := strings.Split(path, "/")
|
||||||
|
nonEmptyParts := []string{}
|
||||||
|
for _, part := range pathParts {
|
||||||
|
if part != "" {
|
||||||
|
nonEmptyParts = append(nonEmptyParts, part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanPathString := ROOT_FOLDER_PATH + strings.Join(nonEmptyParts, "/")
|
||||||
|
return cleanPathString
|
||||||
|
}
|
||||||
|
@ -17,10 +17,10 @@ import (
|
|||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.SingleEnvironmentVariable, error) {
|
func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.SingleEnvironmentVariable, []api.Folders, error) {
|
||||||
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
|
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
|
||||||
if len(serviceTokenParts) < 4 {
|
if len(serviceTokenParts) < 4 {
|
||||||
return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
|
return nil, nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2])
|
serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2])
|
||||||
@ -32,7 +32,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.Singl
|
|||||||
|
|
||||||
serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient)
|
serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to get service token details. [err=%v]", err)
|
return nil, nil, fmt.Errorf("unable to get service token details. [err=%v]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedSecrets, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
|
encryptedSecrets, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
|
||||||
@ -41,28 +41,28 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.Singl
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag)
|
decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err)
|
return nil, nil, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV)
|
plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to decrypt the required workspace key")
|
return nil, nil, fmt.Errorf("unable to decrypt the required workspace key")
|
||||||
}
|
}
|
||||||
|
|
||||||
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets)
|
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
|
return nil, nil, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return plainTextSecrets, nil
|
return plainTextSecrets, encryptedSecrets.Folders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, workspaceId string, environmentName string, tagSlugs string) ([]models.SingleEnvironmentVariable, error) {
|
func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, workspaceId string, environmentName string, tagSlugs string, secretPath string) ([]models.SingleEnvironmentVariable, []api.Folders, error) {
|
||||||
httpClient := resty.New()
|
httpClient := resty.New()
|
||||||
httpClient.SetAuthToken(JTWToken).
|
httpClient.SetAuthToken(JTWToken).
|
||||||
SetHeader("Accept", "application/json")
|
SetHeader("Accept", "application/json")
|
||||||
@ -73,7 +73,7 @@ func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, work
|
|||||||
|
|
||||||
workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request)
|
workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to get your encrypted workspace key. [err=%v]", err)
|
return nil, nil, fmt.Errorf("unable to get your encrypted workspace key. [err=%v]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedWorkspaceKey, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
|
encryptedWorkspaceKey, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
|
||||||
@ -103,25 +103,26 @@ func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, work
|
|||||||
|
|
||||||
plainTextWorkspaceKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
|
plainTextWorkspaceKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
|
||||||
|
|
||||||
encryptedSecrets, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
|
encryptedSecretsAndFolders, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
|
||||||
WorkspaceId: workspaceId,
|
WorkspaceId: workspaceId,
|
||||||
Environment: environmentName,
|
Environment: environmentName,
|
||||||
TagSlugs: tagSlugs,
|
TagSlugs: tagSlugs,
|
||||||
|
SecretPath: secretPath,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets)
|
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsAndFolders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
|
return nil, nil, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return plainTextSecrets, nil
|
return plainTextSecrets, encryptedSecretsAndFolders.Folders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models.SingleEnvironmentVariable, error) {
|
func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models.SingleEnvironmentVariable, []api.Folders, error) {
|
||||||
var infisicalToken string
|
var infisicalToken string
|
||||||
if params.InfisicalToken == "" {
|
if params.InfisicalToken == "" {
|
||||||
infisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
|
infisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
|
||||||
@ -132,6 +133,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
|
|||||||
isConnected := CheckIsConnectedToInternet()
|
isConnected := CheckIsConnectedToInternet()
|
||||||
var secretsToReturn []models.SingleEnvironmentVariable
|
var secretsToReturn []models.SingleEnvironmentVariable
|
||||||
var errorToReturn error
|
var errorToReturn error
|
||||||
|
var folders []api.Folders
|
||||||
|
|
||||||
if infisicalToken == "" {
|
if infisicalToken == "" {
|
||||||
if isConnected {
|
if isConnected {
|
||||||
@ -144,12 +146,12 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
|
|||||||
|
|
||||||
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
|
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaceFile, err := GetWorkSpaceFromFile()
|
workspaceFile, err := GetWorkSpaceFromFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.WorkspaceId != "" {
|
if params.WorkspaceId != "" {
|
||||||
@ -159,10 +161,10 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
|
|||||||
// Verify environment
|
// Verify environment
|
||||||
err = ValidateEnvironmentName(params.Environment, workspaceFile.WorkspaceId, loggedInUserDetails.UserCredentials)
|
err = ValidateEnvironmentName(params.Environment, workspaceFile.WorkspaceId, loggedInUserDetails.UserCredentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to validate environment name because [err=%s]", err)
|
return nil, nil, fmt.Errorf("unable to validate environment name because [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, params.Environment, params.TagSlugs)
|
secretsToReturn, folders, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, params.Environment, params.TagSlugs, params.Path)
|
||||||
log.Debugf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn)
|
log.Debugf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn)
|
||||||
|
|
||||||
backupSecretsEncryptionKey := []byte(loggedInUserDetails.UserCredentials.PrivateKey)[0:32]
|
backupSecretsEncryptionKey := []byte(loggedInUserDetails.UserCredentials.PrivateKey)[0:32]
|
||||||
@ -182,10 +184,10 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.Debug("Trying to fetch secrets using service token")
|
log.Debug("Trying to fetch secrets using service token")
|
||||||
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken)
|
secretsToReturn, folders, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
return secretsToReturn, errorToReturn
|
return secretsToReturn, folders, errorToReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateEnvironmentName(environmentName string, workspaceId string, userLoggedInDetails models.UserCredentials) error {
|
func ValidateEnvironmentName(environmentName string, workspaceId string, userLoggedInDetails models.UserCredentials) error {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package visualize
|
package visualize
|
||||||
|
|
||||||
import "github.com/Infisical/infisical-merge/packages/models"
|
import (
|
||||||
|
"github.com/Infisical/infisical-merge/packages/api"
|
||||||
|
"github.com/Infisical/infisical-merge/packages/models"
|
||||||
|
)
|
||||||
|
|
||||||
func PrintAllSecretDetails(secrets []models.SingleEnvironmentVariable) {
|
func PrintAllSecretDetails(secrets []models.SingleEnvironmentVariable) {
|
||||||
rows := [][3]string{}
|
rows := [][3]string{}
|
||||||
@ -10,5 +13,16 @@ func PrintAllSecretDetails(secrets []models.SingleEnvironmentVariable) {
|
|||||||
|
|
||||||
headers := [...]string{"SECRET NAME", "SECRET VALUE", "SECRET TYPE"}
|
headers := [...]string{"SECRET NAME", "SECRET VALUE", "SECRET TYPE"}
|
||||||
|
|
||||||
|
SecretsTable(headers, rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintSecretFolders(folders []api.Folders) {
|
||||||
|
rows := [][]string{}
|
||||||
|
for _, folder := range folders {
|
||||||
|
rows = append(rows, []string{folder.Name})
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"FOLDER NAME(S)"}
|
||||||
|
|
||||||
Table(headers, rows)
|
Table(headers, rows)
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,36 @@ const (
|
|||||||
ellipsis = "…"
|
ellipsis = "…"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Given any number of headers and rows, this function will print out a table
|
||||||
|
func Table(headers []string, rows [][]string) {
|
||||||
|
t := table.NewWriter()
|
||||||
|
t.SetOutputMirror(os.Stdout)
|
||||||
|
t.SetStyle(table.StyleLight)
|
||||||
|
|
||||||
|
// t.SetTitle("Title")
|
||||||
|
t.Style().Options.DrawBorder = true
|
||||||
|
t.Style().Options.SeparateHeader = true
|
||||||
|
t.Style().Options.SeparateColumns = true
|
||||||
|
|
||||||
|
tableHeaders := table.Row{}
|
||||||
|
for _, header := range headers {
|
||||||
|
tableHeaders = append(tableHeaders, header)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AppendHeader(tableHeaders)
|
||||||
|
for _, row := range rows {
|
||||||
|
tableRow := table.Row{}
|
||||||
|
for _, val := range row {
|
||||||
|
tableRow = append(tableRow, val)
|
||||||
|
}
|
||||||
|
t.AppendRow(tableRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
|
||||||
// Given headers and rows, this function will print out a table
|
// Given headers and rows, this function will print out a table
|
||||||
func Table(headers [3]string, rows [][3]string) {
|
func SecretsTable(headers [3]string, rows [][3]string) {
|
||||||
// if we're not in a terminal or cygwin terminal, don't truncate the secret value
|
// if we're not in a terminal or cygwin terminal, don't truncate the secret value
|
||||||
shouldTruncate := isatty.IsTerminal(os.Stdout.Fd())
|
shouldTruncate := isatty.IsTerminal(os.Stdout.Fd())
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user