mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 18:27:36 +00:00
Merge pull request #338 from Infisical/smoothen-integrations
Smoothen integrations
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import to from 'await-to-js';
|
import to from 'await-to-js';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { ISecret, Membership, Secret, Workspace } from '../../models';
|
import { ISecret, Secret } from '../../models';
|
||||||
|
import { IAction } from '../../ee/models';
|
||||||
import {
|
import {
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED,
|
SECRET_SHARED,
|
||||||
@@ -20,6 +21,252 @@ import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
|
|||||||
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
||||||
import Tag from '../../models/tag';
|
import Tag from '../../models/tag';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import {
|
||||||
|
BatchSecretRequest,
|
||||||
|
BatchSecret
|
||||||
|
} from '../../types/secret';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Peform a batch of any specified CUD secret operations
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const batchSecrets = async (req: Request, res: Response) => {
|
||||||
|
const channel = getChannelFromUserAgent(req.headers['user-agent']);
|
||||||
|
const {
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
requests
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
requests: BatchSecretRequest[];
|
||||||
|
}= req.body;
|
||||||
|
|
||||||
|
const createSecrets: BatchSecret[] = [];
|
||||||
|
const updateSecrets: BatchSecret[] = [];
|
||||||
|
const deleteSecrets: Types.ObjectId[] = [];
|
||||||
|
const actions: IAction[] = [];
|
||||||
|
|
||||||
|
requests.forEach((request) => {
|
||||||
|
switch (request.method) {
|
||||||
|
case 'POST':
|
||||||
|
createSecrets.push({
|
||||||
|
...request.secret,
|
||||||
|
version: 1,
|
||||||
|
user: request.secret.type === SECRET_PERSONAL ? req.user : undefined,
|
||||||
|
environment,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'PATCH':
|
||||||
|
updateSecrets.push({
|
||||||
|
...request.secret,
|
||||||
|
_id: new Types.ObjectId(request.secret._id)
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'DELETE':
|
||||||
|
deleteSecrets.push(new Types.ObjectId(request.secret._id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle create secrets
|
||||||
|
let createdSecrets: ISecret[] = [];
|
||||||
|
if (createSecrets.length > 0) {
|
||||||
|
createdSecrets = await Secret.insertMany(createSecrets);
|
||||||
|
// (EE) add secret versions for new secrets
|
||||||
|
await EESecretService.addSecretVersions({
|
||||||
|
secretVersions: createdSecrets.map((n: any) => {
|
||||||
|
return ({
|
||||||
|
...n._doc,
|
||||||
|
_id: new Types.ObjectId(),
|
||||||
|
secret: n._id,
|
||||||
|
isDeleted: false
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const addAction = await EELogService.createAction({
|
||||||
|
name: ACTION_ADD_SECRETS,
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
secretIds: createdSecrets.map((n) => n._id)
|
||||||
|
}) as IAction;
|
||||||
|
actions.push(addAction);
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: 'secrets added',
|
||||||
|
distinctId: req.user.email,
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: createdSecrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel,
|
||||||
|
userAgent: req.headers?.['user-agent']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle update secrets
|
||||||
|
let updatedSecrets: ISecret[] = [];
|
||||||
|
if (updateSecrets.length > 0 && req.secrets) {
|
||||||
|
// construct object containing all secrets
|
||||||
|
let listedSecretsObj: {
|
||||||
|
[key: string]: {
|
||||||
|
version: number;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
listedSecretsObj = req.secrets.reduce((obj: any, secret: ISecret) => ({
|
||||||
|
...obj,
|
||||||
|
[secret._id.toString()]: secret
|
||||||
|
}), {});
|
||||||
|
|
||||||
|
const updateOperations = updateSecrets.map((u) => ({
|
||||||
|
updateOne: {
|
||||||
|
filter: { _id: new Types.ObjectId(u._id) },
|
||||||
|
update: {
|
||||||
|
$inc: {
|
||||||
|
version: 1
|
||||||
|
},
|
||||||
|
...u,
|
||||||
|
_id: new Types.ObjectId(u._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Secret.bulkWrite(updateOperations);
|
||||||
|
|
||||||
|
const secretVersions = updateSecrets.map((u) => ({
|
||||||
|
secret: new Types.ObjectId(u._id),
|
||||||
|
version: listedSecretsObj[u._id.toString()].version,
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
type: listedSecretsObj[u._id.toString()].type,
|
||||||
|
environment,
|
||||||
|
isDeleted: false,
|
||||||
|
secretKeyCiphertext: u.secretKeyCiphertext,
|
||||||
|
secretKeyIV: u.secretKeyIV,
|
||||||
|
secretKeyTag: u.secretKeyTag,
|
||||||
|
secretValueCiphertext: u.secretValueCiphertext,
|
||||||
|
secretValueIV: u.secretValueIV,
|
||||||
|
secretValueTag: u.secretValueTag,
|
||||||
|
secretCommentCiphertext: u.secretCommentCiphertext,
|
||||||
|
secretCommentIV: u.secretCommentIV,
|
||||||
|
secretCommentTag: u.secretCommentTag,
|
||||||
|
tags: u.tags
|
||||||
|
}));
|
||||||
|
|
||||||
|
await EESecretService.addSecretVersions({
|
||||||
|
secretVersions
|
||||||
|
});
|
||||||
|
|
||||||
|
updatedSecrets = await Secret.find({
|
||||||
|
_id: {
|
||||||
|
$in: updateSecrets.map((u) => new Types.ObjectId(u._id))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateAction = await EELogService.createAction({
|
||||||
|
name: ACTION_UPDATE_SECRETS,
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
secretIds: updatedSecrets.map((u) => u._id)
|
||||||
|
}) as IAction;
|
||||||
|
actions.push(updateAction);
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: 'secrets modified',
|
||||||
|
distinctId: req.user.email,
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: updateSecrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel,
|
||||||
|
userAgent: req.headers?.['user-agent']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle delete secrets
|
||||||
|
if (deleteSecrets.length > 0) {
|
||||||
|
await Secret.deleteMany({
|
||||||
|
_id: {
|
||||||
|
$in: deleteSecrets
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await EESecretService.markDeletedSecretVersions({
|
||||||
|
secretIds: deleteSecrets
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteAction = await EELogService.createAction({
|
||||||
|
name: ACTION_DELETE_SECRETS,
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
secretIds: deleteSecrets
|
||||||
|
}) as IAction;
|
||||||
|
actions.push(deleteAction);
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: 'secrets deleted',
|
||||||
|
distinctId: req.user.email,
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: deleteSecrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel: channel,
|
||||||
|
userAgent: req.headers?.['user-agent']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions.length > 0) {
|
||||||
|
// (EE) create (audit) log
|
||||||
|
await EELogService.createLog({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
actions,
|
||||||
|
channel,
|
||||||
|
ipAddress: req.ip
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// // trigger event - push secrets
|
||||||
|
await EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// (EE) take a secret snapshot
|
||||||
|
await EESecretService.takeSecretSnapshot({
|
||||||
|
workspaceId
|
||||||
|
});
|
||||||
|
|
||||||
|
const resObj: { [key: string]: ISecret[] | string[] } = {}
|
||||||
|
|
||||||
|
if (createSecrets.length > 0) {
|
||||||
|
resObj['createdSecrets'] = createdSecrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateSecrets.length > 0) {
|
||||||
|
resObj['updatedSecrets'] = updatedSecrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteSecrets.length > 0) {
|
||||||
|
resObj['deletedSecrets'] = deleteSecrets.map((d) => d.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(resObj);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create secret(s) for workspace with id [workspaceId] and environment [environment]
|
* Create secret(s) for workspace with id [workspaceId] and environment [environment]
|
||||||
@@ -166,11 +413,9 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretKeyHash,
|
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretValueHash,
|
|
||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
@@ -187,11 +432,9 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretKeyHash,
|
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretValueHash,
|
|
||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
|
|||||||
@@ -158,11 +158,9 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
|||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretKeyHash,
|
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretValueHash
|
|
||||||
} = oldSecretVersion;
|
} = oldSecretVersion;
|
||||||
|
|
||||||
// update secret
|
// update secret
|
||||||
@@ -179,11 +177,9 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
|||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretKeyHash,
|
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretValueHash
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true
|
||||||
@@ -204,11 +200,9 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
|||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretKeyHash,
|
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag
|
||||||
secretValueHash
|
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
// take secret snapshot
|
// take secret snapshot
|
||||||
|
|||||||
@@ -5,22 +5,19 @@ import {
|
|||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
|
|
||||||
export interface ISecretVersion {
|
export interface ISecretVersion {
|
||||||
_id: Types.ObjectId;
|
|
||||||
secret: Types.ObjectId;
|
secret: Types.ObjectId;
|
||||||
version: number;
|
version: number;
|
||||||
workspace: Types.ObjectId; // new
|
workspace: Types.ObjectId; // new
|
||||||
type: string; // new
|
type: string; // new
|
||||||
user: Types.ObjectId; // new
|
user?: Types.ObjectId; // new
|
||||||
environment: string; // new
|
environment: string; // new
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
secretKeyCiphertext: string;
|
secretKeyCiphertext: string;
|
||||||
secretKeyIV: string;
|
secretKeyIV: string;
|
||||||
secretKeyTag: string;
|
secretKeyTag: string;
|
||||||
secretKeyHash: string;
|
|
||||||
secretValueCiphertext: string;
|
secretValueCiphertext: string;
|
||||||
secretValueIV: string;
|
secretValueIV: string;
|
||||||
secretValueTag: string;
|
secretValueTag: string;
|
||||||
secretValueHash: string;
|
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,9 +69,6 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
|||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
secretKeyHash: {
|
|
||||||
type: String
|
|
||||||
},
|
|
||||||
secretValueCiphertext: {
|
secretValueCiphertext: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
@@ -87,9 +81,6 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
|||||||
type: String, // symmetric
|
type: String, // symmetric
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
secretValueHash: {
|
|
||||||
type: String
|
|
||||||
},
|
|
||||||
tags: {
|
tags: {
|
||||||
ref: 'Tag',
|
ref: 'Tag',
|
||||||
type: [Schema.Types.ObjectId],
|
type: [Schema.Types.ObjectId],
|
||||||
|
|||||||
@@ -6,14 +6,52 @@ import {
|
|||||||
requireSecretsAuth,
|
requireSecretsAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { query, check, body } from 'express-validator';
|
import { query, body } from 'express-validator';
|
||||||
import { secretsController } from '../../controllers/v2';
|
import { secretsController } from '../../controllers/v2';
|
||||||
|
import { validateSecrets } from '../../helpers/secret';
|
||||||
import {
|
import {
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED
|
SECRET_SHARED
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
|
import {
|
||||||
|
BatchSecretRequest
|
||||||
|
} from '../../types/secret';
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/batch',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
location: 'body'
|
||||||
|
}),
|
||||||
|
body('workspaceId').exists().isString().trim(),
|
||||||
|
body('environment').exists().isString().trim(),
|
||||||
|
body('requests')
|
||||||
|
.exists()
|
||||||
|
.custom(async (requests: BatchSecretRequest[], { req }) => {
|
||||||
|
if (Array.isArray(requests)) {
|
||||||
|
const secretIds = requests
|
||||||
|
.map((request) => request.secret._id)
|
||||||
|
.filter((secretId) => secretId !== undefined)
|
||||||
|
|
||||||
|
if (secretIds.length > 0) {
|
||||||
|
const relevantSecrets = await validateSecrets({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
secretIds
|
||||||
|
});
|
||||||
|
|
||||||
|
req.secrets = relevantSecrets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
validateRequest,
|
||||||
|
secretsController.batchSecrets
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
|
|||||||
Vendored
+38
@@ -1,5 +1,7 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
import { Assign, Omit } from 'utility-types';
|
import { Assign, Omit } from 'utility-types';
|
||||||
import { ISecret } from '../../models';
|
import { ISecret } from '../../models';
|
||||||
|
import { mongo } from 'mongoose';
|
||||||
|
|
||||||
// Everything is required, except the omitted types
|
// Everything is required, except the omitted types
|
||||||
export type CreateSecretRequestBody = Omit<ISecret, "user" | "version" | "environment" | "workspace">;
|
export type CreateSecretRequestBody = Omit<ISecret, "user" | "version" | "environment" | "workspace">;
|
||||||
@@ -12,3 +14,39 @@ export type SanitizedSecretModify = Partial<Omit<ISecret, "user" | "version" | "
|
|||||||
|
|
||||||
// Everything is required, except the omitted types
|
// Everything is required, except the omitted types
|
||||||
export type SanitizedSecretForCreate = Omit<ISecret, "version" | "_id">;
|
export type SanitizedSecretForCreate = Omit<ISecret, "version" | "_id">;
|
||||||
|
|
||||||
|
export interface BatchSecretRequest {
|
||||||
|
id: string;
|
||||||
|
method: 'POST' | 'PATCH' | 'DELETE';
|
||||||
|
secret: Secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BatchSecret {
|
||||||
|
_id: string;
|
||||||
|
type: 'shared' | 'personal',
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretCommentCiphertext: string;
|
||||||
|
secretCommentIV: string;
|
||||||
|
secretCommentTag: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BatchSecret {
|
||||||
|
_id: string;
|
||||||
|
type: 'shared' | 'personal',
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretCommentCiphertext: string;
|
||||||
|
secretCommentIV: string;
|
||||||
|
secretCommentTag: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@ import { Switch } from '@headlessui/react';
|
|||||||
interface ToggleProps {
|
interface ToggleProps {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
setEnabled: (value: boolean) => void;
|
setEnabled: (value: boolean) => void;
|
||||||
addOverride: (value: string | undefined, pos: number) => void;
|
addOverride: (value: string | undefined, id: string) => void;
|
||||||
pos: number;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,18 +13,18 @@ interface ToggleProps {
|
|||||||
* @param {boolean} obj.enabled - whether the toggle is turned on or off
|
* @param {boolean} obj.enabled - whether the toggle is turned on or off
|
||||||
* @param {function} obj.setEnabled - change the state of the toggle
|
* @param {function} obj.setEnabled - change the state of the toggle
|
||||||
* @param {function} obj.addOverride - a function that adds an override to a certain secret
|
* @param {function} obj.addOverride - a function that adds an override to a certain secret
|
||||||
* @param {number} obj.pos - position of a certain secret
|
* @param {number} obj.id - id of a certain secret
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const Toggle = ({ enabled, setEnabled, addOverride, pos }: ToggleProps): JSX.Element => {
|
const Toggle = ({ enabled, setEnabled, addOverride, id }: ToggleProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
if (enabled === false) {
|
if (enabled === false) {
|
||||||
addOverride('', pos);
|
addOverride('', id);
|
||||||
} else {
|
} else {
|
||||||
addOverride(undefined, pos);
|
addOverride(undefined, id);
|
||||||
}
|
}
|
||||||
setEnabled(!enabled);
|
setEnabled(!enabled);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -45,19 +45,19 @@ export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
|
|||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker border border-mineshaft-600 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-bunker-200">
|
||||||
{t('dashboard:sidebar.delete-key-dialog.title')}
|
{t('dashboard:sidebar.delete-key-dialog.title')}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-bunker-300">
|
||||||
{t('dashboard:sidebar.delete-key-dialog.confirm-delete-message')}
|
{t('dashboard:sidebar.delete-key-dialog.confirm-delete-message')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 flex justify-start">
|
<div className="mt-6 flex justify-start">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex justify-center rounded-md border border-transparent bg-red-700 hover:bg-red-600 px-4 py-2 text-sm font-medium text-bunker-200 hover:text-white text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
className="inline-flex justify-center rounded-md border border-transparent bg-red-500 opacity-80 hover:opacity-100 px-4 py-2 text-sm font-medium text-bunker-100 text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { Tag } from 'public/data/frequentInterfaces';
|
|||||||
* @param {function} obj.modifyTags - modify tags for a certain secret
|
* @param {function} obj.modifyTags - modify tags for a certain secret
|
||||||
* @param {Tag[]} obj.position - currently selected tags for a certain secret
|
* @param {Tag[]} obj.position - currently selected tags for a certain secret
|
||||||
*/
|
*/
|
||||||
const AddTagsMenu = ({ allTags, currentTags, modifyTags, position }: { allTags: Tag[]; currentTags: Tag[]; modifyTags: (value: Tag[], position: number) => void; position: number; }) => {
|
const AddTagsMenu = ({ allTags, currentTags, modifyTags, id }: { allTags: Tag[]; currentTags: Tag[]; modifyTags: (value: Tag[], id: string) => void; id: string; }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
return (
|
return (
|
||||||
<Menu as="div" className="ml-2 relative inline-block text-left">
|
<Menu as="div" className="ml-2 relative inline-block text-left">
|
||||||
@@ -41,7 +41,7 @@ const AddTagsMenu = ({ allTags, currentTags, modifyTags, position }: { allTags:
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`${currentTags?.map(currentTag => currentTag.name).includes(tag.name) ? "opacity-30 cursor-default" : "hover:bg-mineshaft-700"} w-full text-left bg-mineshaft-800 px-2 py-0.5 text-bunker-200 rounded-sm flex items-center`}
|
className={`${currentTags?.map(currentTag => currentTag.name).includes(tag.name) ? "opacity-30 cursor-default" : "hover:bg-mineshaft-700"} w-full text-left bg-mineshaft-800 px-2 py-0.5 text-bunker-200 rounded-sm flex items-center`}
|
||||||
onClick={() => {if (!currentTags?.map(currentTag => currentTag.name).includes(tag.name)) {modifyTags(currentTags.concat([tag]), position)}}}
|
onClick={() => {if (!currentTags?.map(currentTag => currentTag.name).includes(tag.name)) {modifyTags(currentTags.concat([tag]), id)}}}
|
||||||
>
|
>
|
||||||
{currentTags?.map(currentTag => currentTag.name).includes(tag.name) ? <FontAwesomeIcon icon={faCheckSquare} className="text-xs mr-2 text-primary"/> : <FontAwesomeIcon icon={faSquare} className="text-xs mr-2"/>} {tag.name}
|
{currentTags?.map(currentTag => currentTag.name).includes(tag.name) ? <FontAwesomeIcon icon={faCheckSquare} className="text-xs mr-2 text-primary"/> : <FontAwesomeIcon icon={faSquare} className="text-xs mr-2"/>} {tag.name}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import { useTranslation } from 'next-i18next';
|
|||||||
const CommentField = ({
|
const CommentField = ({
|
||||||
comment,
|
comment,
|
||||||
modifyComment,
|
modifyComment,
|
||||||
position
|
id
|
||||||
}: {
|
}: {
|
||||||
comment: string;
|
comment: string;
|
||||||
modifyComment: (value: string, posistion: number) => void;
|
modifyComment: (value: string, id: string) => void;
|
||||||
position: number;
|
id: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ const CommentField = ({
|
|||||||
<textarea
|
<textarea
|
||||||
className="placeholder:text-bunker-400 dark:[color-scheme:dark] h-32 w-full bg-bunker-800 px-2 py-1.5 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
|
className="placeholder:text-bunker-400 dark:[color-scheme:dark] h-32 w-full bg-bunker-800 px-2 py-1.5 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={(e) => modifyComment(e.target.value, position)}
|
onChange={(e) => modifyComment(e.target.value, id)}
|
||||||
placeholder="Leave any comments here..."
|
placeholder="Leave any comments here..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ import { PopoverObject } from '../v2/Popover/Popover';
|
|||||||
const REGEX = /([$]{.*?})/g;
|
const REGEX = /([$]{.*?})/g;
|
||||||
|
|
||||||
interface DashboardInputFieldProps {
|
interface DashboardInputFieldProps {
|
||||||
position: number;
|
id: string;
|
||||||
onChangeHandler: (value: string, position: number) => void;
|
onChangeHandler: (value: string, id: string) => void;
|
||||||
value: string | undefined;
|
value: string | undefined;
|
||||||
type: 'varName' | 'value' | 'comment';
|
type: 'varName' | 'value' | 'comment';
|
||||||
blurred?: boolean;
|
blurred?: boolean;
|
||||||
isDuplicate?: boolean;
|
isDuplicate?: boolean;
|
||||||
isCapitalized?: boolean;
|
isCapitalized?: boolean;
|
||||||
overrideEnabled?: boolean;
|
overrideEnabled?: boolean;
|
||||||
modifyValueOverride?: (value: string | undefined, position: number) => void;
|
modifyValueOverride?: (value: string | undefined, id: string) => void;
|
||||||
isSideBarOpen?: boolean;
|
isSideBarOpen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ interface DashboardInputFieldProps {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const DashboardInputField = ({
|
const DashboardInputField = ({
|
||||||
position,
|
id,
|
||||||
onChangeHandler,
|
onChangeHandler,
|
||||||
type,
|
type,
|
||||||
value,
|
value,
|
||||||
@@ -72,7 +72,7 @@ const DashboardInputField = ({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, position)}
|
onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)}
|
||||||
type={type}
|
type={type}
|
||||||
value={value}
|
value={value}
|
||||||
className={`z-10 peer font-mono ph-no-capture bg-transparent h-full caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none ${
|
className={`z-10 peer font-mono ph-no-capture bg-transparent h-full caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none ${
|
||||||
@@ -105,9 +105,9 @@ const DashboardInputField = ({
|
|||||||
<button type="button" onClick={() => {
|
<button type="button" onClick={() => {
|
||||||
if (modifyValueOverride) {
|
if (modifyValueOverride) {
|
||||||
if (overrideEnabled === false) {
|
if (overrideEnabled === false) {
|
||||||
modifyValueOverride('', position);
|
modifyValueOverride('', id);
|
||||||
} else {
|
} else {
|
||||||
modifyValueOverride(undefined, position);
|
modifyValueOverride(undefined, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
@@ -126,7 +126,7 @@ const DashboardInputField = ({
|
|||||||
const error = startsWithNumber || isDuplicate;
|
const error = startsWithNumber || isDuplicate;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverObject text={value || ''} onChangeHandler={onChangeHandler} position={position}>
|
<PopoverObject text={value || ''} onChangeHandler={onChangeHandler} id={id}>
|
||||||
<div title={value} className={`relative flex-col w-full h-10 overflow-hidden ${
|
<div title={value} className={`relative flex-col w-full h-10 overflow-hidden ${
|
||||||
isSideBarOpen && 'bg-mineshaft-700 duration-200'
|
isSideBarOpen && 'bg-mineshaft-700 duration-200'
|
||||||
}`}>
|
}`}>
|
||||||
@@ -157,7 +157,7 @@ const DashboardInputField = ({
|
|||||||
)}
|
)}
|
||||||
<input
|
<input
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChangeHandler(e.target.value, position)}
|
onChange={(e) => onChangeHandler(e.target.value, id)}
|
||||||
onScroll={syncScroll}
|
onScroll={syncScroll}
|
||||||
className={`${
|
className={`${
|
||||||
blurred
|
blurred
|
||||||
@@ -175,10 +175,10 @@ const DashboardInputField = ({
|
|||||||
} ${overrideEnabled ? 'text-primary-300' : 'text-gray-400'}
|
} ${overrideEnabled ? 'text-primary-300' : 'text-gray-400'}
|
||||||
absolute flex flex-row whitespace-pre font-mono z-0 ${blurred ? 'invisible' : 'visible'} peer-focus:visible mt-0.5 ph-no-capture overflow-x-scroll bg-transparent h-10 text-sm px-2 py-2 w-full min-w-16 outline-none duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
|
absolute flex flex-row whitespace-pre font-mono z-0 ${blurred ? 'invisible' : 'visible'} peer-focus:visible mt-0.5 ph-no-capture overflow-x-scroll bg-transparent h-10 text-sm px-2 py-2 w-full min-w-16 outline-none duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
|
||||||
>
|
>
|
||||||
{value?.split(REGEX).map((word, id) => {
|
{value?.split(REGEX).map((word) => {
|
||||||
if (word.match(REGEX) !== null) {
|
if (word.match(REGEX) !== null) {
|
||||||
return (
|
return (
|
||||||
<span className="ph-no-capture text-yellow" key={`${word}.${id + 1}`}>
|
<span className="ph-no-capture text-yellow" key={id}>
|
||||||
{word.slice(0, 2)}
|
{word.slice(0, 2)}
|
||||||
<span className="ph-no-capture text-yellow-200/80">
|
<span className="ph-no-capture text-yellow-200/80">
|
||||||
{word.slice(2, word.length - 1)}
|
{word.slice(2, word.length - 1)}
|
||||||
@@ -231,7 +231,7 @@ function inputPropsAreEqual(prev: DashboardInputFieldProps, next: DashboardInput
|
|||||||
return (
|
return (
|
||||||
prev.value === next.value &&
|
prev.value === next.value &&
|
||||||
prev.type === next.type &&
|
prev.type === next.type &&
|
||||||
prev.position === next.position &&
|
prev.id === next.id &&
|
||||||
prev.blurred === next.blurred &&
|
prev.blurred === next.blurred &&
|
||||||
prev.isCapitalized === next.isCapitalized &&
|
prev.isCapitalized === next.isCapitalized &&
|
||||||
prev.overrideEnabled === next.overrideEnabled &&
|
prev.overrideEnabled === next.overrideEnabled &&
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import { Menu, Transition } from '@headlessui/react';
|
|||||||
*/
|
*/
|
||||||
const GenerateSecretMenu = ({
|
const GenerateSecretMenu = ({
|
||||||
modifyValue,
|
modifyValue,
|
||||||
position
|
id
|
||||||
}: {
|
}: {
|
||||||
modifyValue: (value: string, position: number) => void;
|
modifyValue: (value: string, id: string) => void;
|
||||||
position: number;
|
id: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [randomStringLength, setRandomStringLength] = useState(32);
|
const [randomStringLength, setRandomStringLength] = useState(32);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -51,7 +51,7 @@ const GenerateSecretMenu = ({
|
|||||||
[...Array(randomStringLength)]
|
[...Array(randomStringLength)]
|
||||||
.map(() => Math.floor(Math.random() * 16).toString(16))
|
.map(() => Math.floor(Math.random() * 16).toString(16))
|
||||||
.join(''),
|
.join(''),
|
||||||
position
|
id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import { DeleteActionButton } from './DeleteActionButton';
|
|||||||
|
|
||||||
interface KeyPairProps {
|
interface KeyPairProps {
|
||||||
keyPair: SecretDataProps;
|
keyPair: SecretDataProps;
|
||||||
modifyKey: (value: string, position: number) => void;
|
modifyKey: (value: string, id: string) => void;
|
||||||
modifyValue: (value: string, position: number) => void;
|
modifyValue: (value: string, id: string) => void;
|
||||||
modifyValueOverride: (value: string | undefined, position: number) => void;
|
modifyValueOverride: (value: string | undefined, id: string) => void;
|
||||||
modifyComment: (value: string, position: number) => void;
|
modifyComment: (value: string, id: string) => void;
|
||||||
modifyTags: (value: Tag[], position: number) => void;
|
modifyTags: (value: Tag[], id: string) => void;
|
||||||
isBlurred: boolean;
|
isBlurred: boolean;
|
||||||
isDuplicate: boolean;
|
isDuplicate: boolean;
|
||||||
toggleSidebar: (id: string) => void;
|
toggleSidebar: (id: string) => void;
|
||||||
@@ -108,7 +108,7 @@ const KeyPair = ({
|
|||||||
isCapitalized = {isCapitalized}
|
isCapitalized = {isCapitalized}
|
||||||
onChangeHandler={modifyKey}
|
onChangeHandler={modifyKey}
|
||||||
type="varName"
|
type="varName"
|
||||||
position={keyPair.pos}
|
id={keyPair.id}
|
||||||
value={keyPair.key}
|
value={keyPair.key}
|
||||||
isDuplicate={isDuplicate}
|
isDuplicate={isDuplicate}
|
||||||
overrideEnabled={keyPair.valueOverride !== undefined}
|
overrideEnabled={keyPair.valueOverride !== undefined}
|
||||||
@@ -124,7 +124,7 @@ const KeyPair = ({
|
|||||||
<DashboardInputField
|
<DashboardInputField
|
||||||
onChangeHandler={keyPair.valueOverride !== undefined ? modifyValueOverride : modifyValue}
|
onChangeHandler={keyPair.valueOverride !== undefined ? modifyValueOverride : modifyValue}
|
||||||
type="value"
|
type="value"
|
||||||
position={keyPair.pos}
|
id={keyPair.id}
|
||||||
value={keyPair.valueOverride !== undefined ? keyPair.valueOverride : keyPair.value}
|
value={keyPair.valueOverride !== undefined ? keyPair.valueOverride : keyPair.value}
|
||||||
blurred={isBlurred}
|
blurred={isBlurred}
|
||||||
overrideEnabled={keyPair.valueOverride !== undefined}
|
overrideEnabled={keyPair.valueOverride !== undefined}
|
||||||
@@ -137,7 +137,7 @@ const KeyPair = ({
|
|||||||
<DashboardInputField
|
<DashboardInputField
|
||||||
onChangeHandler={modifyComment}
|
onChangeHandler={modifyComment}
|
||||||
type="comment"
|
type="comment"
|
||||||
position={keyPair.pos}
|
id={keyPair.id}
|
||||||
value={keyPair.comment}
|
value={keyPair.comment}
|
||||||
isDuplicate={isDuplicate}
|
isDuplicate={isDuplicate}
|
||||||
isSideBarOpen={keyPair.id === sidebarSecretId}
|
isSideBarOpen={keyPair.id === sidebarSecretId}
|
||||||
@@ -149,11 +149,11 @@ const KeyPair = ({
|
|||||||
{keyPair.tags?.map((tag, index) => (
|
{keyPair.tags?.map((tag, index) => (
|
||||||
index < 2 && <div key={keyPair.pos} className={`ml-2 px-1.5 ${tagData.filter(tagDp => tagDp._id === tag._id)[0]?.color} rounded-sm text-sm ${tagData.filter(tagDp => tagDp._id === tag._id)[0]?.colorText} flex items-center`}>
|
index < 2 && <div key={keyPair.pos} className={`ml-2 px-1.5 ${tagData.filter(tagDp => tagDp._id === tag._id)[0]?.color} rounded-sm text-sm ${tagData.filter(tagDp => tagDp._id === tag._id)[0]?.colorText} flex items-center`}>
|
||||||
<span className='mb-0.5 cursor-default'>{tag.name}</span>
|
<span className='mb-0.5 cursor-default'>{tag.name}</span>
|
||||||
<FontAwesomeIcon icon={faXmark} className="ml-1 cursor-pointer p-1" onClick={() => modifyTags(keyPair.tags.filter(ttag => ttag._id !== tag._id), keyPair.pos)}/>
|
<FontAwesomeIcon icon={faXmark} className="ml-1 cursor-pointer p-1" onClick={() => modifyTags(keyPair.tags.filter(ttag => ttag._id !== tag._id), keyPair.id)}/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<AddTagsMenu allTags={tags} currentTags={keyPair.tags} modifyTags={modifyTags} position={keyPair.pos} />
|
<AddTagsMenu allTags={tags} currentTags={keyPair.tags} modifyTags={modifyTags} id={keyPair.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ export interface DeleteRowFunctionProps {
|
|||||||
interface SideBarProps {
|
interface SideBarProps {
|
||||||
toggleSidebar: (value: string) => void;
|
toggleSidebar: (value: string) => void;
|
||||||
data: SecretProps[];
|
data: SecretProps[];
|
||||||
modifyKey: (value: string, position: number) => void;
|
modifyKey: (value: string, id: string) => void;
|
||||||
modifyValue: (value: string, position: number) => void;
|
modifyValue: (value: string, id: string) => void;
|
||||||
modifyValueOverride: (value: string | undefined, position: number) => void;
|
modifyValueOverride: (value: string | undefined, id: string) => void;
|
||||||
modifyComment: (value: string, position: number) => void;
|
modifyComment: (value: string, id: string) => void;
|
||||||
buttonReady: boolean;
|
buttonReady: boolean;
|
||||||
savePush: () => void;
|
savePush: () => void;
|
||||||
sharedToHide: string[];
|
sharedToHide: string[];
|
||||||
@@ -110,7 +110,7 @@ const SideBar = ({
|
|||||||
<DashboardInputField
|
<DashboardInputField
|
||||||
onChangeHandler={modifyKey}
|
onChangeHandler={modifyKey}
|
||||||
type="varName"
|
type="varName"
|
||||||
position={data[0]?.pos}
|
id={data[0]?.id}
|
||||||
value={data[0]?.key}
|
value={data[0]?.key}
|
||||||
isDuplicate={false}
|
isDuplicate={false}
|
||||||
blurred={false}
|
blurred={false}
|
||||||
@@ -128,14 +128,14 @@ const SideBar = ({
|
|||||||
<DashboardInputField
|
<DashboardInputField
|
||||||
onChangeHandler={modifyValue}
|
onChangeHandler={modifyValue}
|
||||||
type="value"
|
type="value"
|
||||||
position={data[0].pos}
|
id={data[0].id}
|
||||||
value={data[0]?.value}
|
value={data[0]?.value}
|
||||||
isDuplicate={false}
|
isDuplicate={false}
|
||||||
blurred
|
blurred
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bg-bunker-800 right-[1.07rem] top-[1.6rem] z-50">
|
<div className="absolute bg-bunker-800 right-[1.07rem] top-[1.6rem] z-50">
|
||||||
<GenerateSecretMenu modifyValue={modifyValue} position={data[0]?.pos} />
|
<GenerateSecretMenu modifyValue={modifyValue} id={data[0]?.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -154,7 +154,7 @@ const SideBar = ({
|
|||||||
enabled={overrideEnabled}
|
enabled={overrideEnabled}
|
||||||
setEnabled={setOverrideEnabled}
|
setEnabled={setOverrideEnabled}
|
||||||
addOverride={modifyValueOverride}
|
addOverride={modifyValueOverride}
|
||||||
pos={data[0]?.pos}
|
id={data[0]?.id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -167,14 +167,14 @@ const SideBar = ({
|
|||||||
<DashboardInputField
|
<DashboardInputField
|
||||||
onChangeHandler={modifyValueOverride}
|
onChangeHandler={modifyValueOverride}
|
||||||
type="value"
|
type="value"
|
||||||
position={data[0]?.pos}
|
id={data[0]?.id}
|
||||||
value={overrideEnabled ? data[0]?.valueOverride : data[0]?.value}
|
value={overrideEnabled ? data[0]?.valueOverride : data[0]?.value}
|
||||||
isDuplicate={false}
|
isDuplicate={false}
|
||||||
blurred
|
blurred
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute right-[0.57rem] top-[0.3rem] z-50">
|
<div className="absolute right-[0.57rem] top-[0.3rem] z-50">
|
||||||
<GenerateSecretMenu modifyValue={modifyValueOverride} position={data[0]?.pos} />
|
<GenerateSecretMenu modifyValue={modifyValueOverride} id={data[0]?.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,7 +182,7 @@ const SideBar = ({
|
|||||||
<CommentField
|
<CommentField
|
||||||
comment={data[0]?.comment}
|
comment={data[0]?.comment}
|
||||||
modifyComment={modifyComment}
|
modifyComment={modifyComment}
|
||||||
position={data[0]?.pos}
|
id={data[0]?.id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import * as Popover from '@radix-ui/react-popover';
|
|||||||
type Props = {
|
type Props = {
|
||||||
children: any;
|
children: any;
|
||||||
text: string;
|
text: string;
|
||||||
onChangeHandler: (value: string, position: number) => void;
|
onChangeHandler: (value: string, id: string) => void;
|
||||||
position: number;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PopoverProps = Props;
|
export type PopoverProps = Props;
|
||||||
|
|
||||||
export const PopoverObject = ({children, text, onChangeHandler, position}: Props) => (
|
export const PopoverObject = ({children, text, onChangeHandler, id}: Props) => (
|
||||||
<Popover.Root>
|
<Popover.Root>
|
||||||
<Popover.Trigger asChild className='data-[state=open]:outline data-[state=open]:outline-primary data-[state=closed]:hover:outline data-[state=closed]:hover:outline-mineshaft-400'>
|
<Popover.Trigger asChild className='data-[state=open]:outline data-[state=open]:outline-primary data-[state=closed]:hover:outline data-[state=closed]:hover:outline-mineshaft-400'>
|
||||||
{children}
|
{children}
|
||||||
@@ -26,7 +26,7 @@ export const PopoverObject = ({children, text, onChangeHandler, position}: Props
|
|||||||
<div className="flex flex-col pt-2 dark">
|
<div className="flex flex-col pt-2 dark">
|
||||||
<p className="text-bunker-200 text-[15px] leading-[0px] font-medium mb-5">Comment</p>
|
<p className="text-bunker-200 text-[15px] leading-[0px] font-medium mb-5">Comment</p>
|
||||||
<textarea
|
<textarea
|
||||||
onChange={(e) => onChangeHandler(e.target.value, position)}
|
onChange={(e) => onChangeHandler(e.target.value, id)}
|
||||||
// type={type}
|
// type={type}
|
||||||
value={text}
|
value={text}
|
||||||
className='z-10 dark:[color-scheme:dark] peer h-[20rem] ph-no-capture bg-bunker-600 border border-mineshaft-500 rounded-md py-2.5 caret-bunker-200 text-sm px-2 w-full outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'
|
className='z-10 dark:[color-scheme:dark] peer h-[20rem] ph-no-capture bg-bunker-600 border border-mineshaft-500 rounded-md py-2.5 caret-bunker-200 text-sm px-2 w-full outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
interface RequestType {
|
||||||
|
method: string;
|
||||||
|
secret: {
|
||||||
|
type: string;
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretCommentCiphertext: string;
|
||||||
|
secretCommentIV: string;
|
||||||
|
secretCommentTag: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchSecrets = async ({
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
requests
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
requests: RequestType[];
|
||||||
|
}) => {
|
||||||
|
const { data } = await apiRequest.post('/api/v2/secrets/batch', {
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
requests
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default batchSecrets;
|
||||||
@@ -30,6 +30,7 @@ import DropZone from '@app/components/dashboard/DropZone';
|
|||||||
import KeyPair from '@app/components/dashboard/KeyPair';
|
import KeyPair from '@app/components/dashboard/KeyPair';
|
||||||
import SideBar from '@app/components/dashboard/SideBar';
|
import SideBar from '@app/components/dashboard/SideBar';
|
||||||
import NavHeader from '@app/components/navigation/NavHeader';
|
import NavHeader from '@app/components/navigation/NavHeader';
|
||||||
|
import { decryptAssymmetric, decryptSymmetric } from '@app/components/utilities/cryptography/crypto';
|
||||||
import guidGenerator from '@app/components/utilities/randomId';
|
import guidGenerator from '@app/components/utilities/randomId';
|
||||||
import encryptSecrets from '@app/components/utilities/secrets/encryptSecrets';
|
import encryptSecrets from '@app/components/utilities/secrets/encryptSecrets';
|
||||||
import getSecretsForProject from '@app/components/utilities/secrets/getSecretsForProject';
|
import getSecretsForProject from '@app/components/utilities/secrets/getSecretsForProject';
|
||||||
@@ -41,12 +42,14 @@ import performSecretRollback from '@app/ee/api/secrets/PerformSecretRollback';
|
|||||||
import PITRecoverySidebar from '@app/ee/components/PITRecoverySidebar';
|
import PITRecoverySidebar from '@app/ee/components/PITRecoverySidebar';
|
||||||
import { useLeaveConfirm } from '@app/hooks';
|
import { useLeaveConfirm } from '@app/hooks';
|
||||||
|
|
||||||
import addSecrets from '../api/files/AddSecrets';
|
// import addSecrets from '../api/files/AddSecrets';
|
||||||
import deleteSecrets from '../api/files/DeleteSecrets';
|
// import deleteSecrets from '../api/files/DeleteSecrets';
|
||||||
import updateSecrets from '../api/files/UpdateSecrets';
|
// import updateSecrets from '../api/files/UpdateSecrets';
|
||||||
|
import batchSecrets from '../api/files/batchSecrets';
|
||||||
import getUser from '../api/user/getUser';
|
import getUser from '../api/user/getUser';
|
||||||
import checkUserAction from '../api/userActions/checkUserAction';
|
import checkUserAction from '../api/userActions/checkUserAction';
|
||||||
import registerUserAction from '../api/userActions/registerUserAction';
|
import registerUserAction from '../api/userActions/registerUserAction';
|
||||||
|
import getLatestFileKey from '../api/workspace/getLatestFileKey';
|
||||||
import getWorkspaceEnvironments from '../api/workspace/getWorkspaceEnvironments';
|
import getWorkspaceEnvironments from '../api/workspace/getWorkspaceEnvironments';
|
||||||
import getWorkspaces from '../api/workspace/getWorkspaces';
|
import getWorkspaces from '../api/workspace/getWorkspaces';
|
||||||
import getWorkspaceTags from '../api/workspace/getWorkspaceTags';
|
import getWorkspaceTags from '../api/workspace/getWorkspaceTags';
|
||||||
@@ -94,6 +97,32 @@ interface SnapshotProps {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EncryptedSecretProps {
|
||||||
|
_id: string;
|
||||||
|
createdAt: string;
|
||||||
|
environment: string;
|
||||||
|
secretCommentCiphertext: string;
|
||||||
|
secretCommentIV: string;
|
||||||
|
secretCommentTag: string;
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
type: 'personal' | 'shared';
|
||||||
|
tags: Tag[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecretProps {
|
||||||
|
key: string;
|
||||||
|
value: string | undefined;
|
||||||
|
type: 'personal' | 'shared';
|
||||||
|
comment: string;
|
||||||
|
id: string;
|
||||||
|
tags: Tag[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this function finds the teh duplicates in an array
|
* this function finds the teh duplicates in an array
|
||||||
* @param arr - array of anything (e.g., with secret keys and types (personal/shared))
|
* @param arr - array of anything (e.g., with secret keys and types (personal/shared))
|
||||||
@@ -302,50 +331,50 @@ export default function Dashboard() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifyValue = (value: string, pos: number) => {
|
const modifyValue = (value: string, id: string) => {
|
||||||
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, value } : e)));
|
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, value } : e)));
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifyValueOverride = (valueOverride: string | undefined, pos: number) => {
|
const modifyValueOverride = (valueOverride: string | undefined, id: string) => {
|
||||||
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, valueOverride } : e)));
|
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, valueOverride } : e)));
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifyKey = (key: string, pos: number) => {
|
const modifyKey = (key: string, id: string) => {
|
||||||
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, key } : e)));
|
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, key } : e)));
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifyComment = (comment: string, pos: number) => {
|
const modifyComment = (comment: string, id: string) => {
|
||||||
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, comment } : e)));
|
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, comment } : e)));
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifyTags = (tags: Tag[], pos: number) => {
|
const modifyTags = (tags: Tag[], id: string) => {
|
||||||
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, tags } : e)));
|
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, tags } : e)));
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// For speed purposes and better perforamance, we are using useCallback
|
// For speed purposes and better perforamance, we are using useCallback
|
||||||
const listenChangeValue = useCallback((value: string, pos: number) => {
|
const listenChangeValue = useCallback((value: string, id: string) => {
|
||||||
modifyValue(value, pos);
|
modifyValue(value, id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const listenChangeValueOverride = useCallback((value: string | undefined, pos: number) => {
|
const listenChangeValueOverride = useCallback((value: string | undefined, id: string) => {
|
||||||
modifyValueOverride(value, pos);
|
modifyValueOverride(value, id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const listenChangeKey = useCallback((value: string, pos: number) => {
|
const listenChangeKey = useCallback((value: string, id: string) => {
|
||||||
modifyKey(value, pos);
|
modifyKey(value, id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const listenChangeComment = useCallback((value: string, pos: number) => {
|
const listenChangeComment = useCallback((value: string, id: string) => {
|
||||||
modifyComment(value, pos);
|
modifyComment(value, id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const listenChangeTags = useCallback((value: Tag[], pos: number) => {
|
const listenChangeTags = useCallback((value: Tag[], id: string) => {
|
||||||
modifyTags(value, pos);
|
modifyTags(value, id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -353,174 +382,331 @@ export default function Dashboard() {
|
|||||||
*/
|
*/
|
||||||
// TODO(akhilmhdh): split and make it small
|
// TODO(akhilmhdh): split and make it small
|
||||||
const savePush = async (dataToPush?: SecretDataProps[]) => {
|
const savePush = async (dataToPush?: SecretDataProps[]) => {
|
||||||
setSaveLoading(true);
|
try {
|
||||||
let newData: SecretDataProps[] | null | undefined;
|
setSaveLoading(true);
|
||||||
// dataToPush is mostly used for rollbacks, otherwise we always take the current state data
|
let newData: SecretDataProps[] | null | undefined;
|
||||||
if ((dataToPush ?? [])?.length > 0) {
|
// dataToPush is mostly used for rollbacks, otherwise we always take the current state data
|
||||||
newData = dataToPush;
|
if ((dataToPush ?? [])?.length > 0) {
|
||||||
} else {
|
newData = dataToPush;
|
||||||
newData = data;
|
} else {
|
||||||
}
|
newData = data;
|
||||||
|
}
|
||||||
|
|
||||||
// Checking if any of the secret keys start with a number - if so, don't do anything
|
// Checking if any of the secret keys start with a number - if so, don't do anything
|
||||||
const nameErrors = !newData!
|
const nameErrors = !newData!
|
||||||
.map((secret) => !Number.isNaN(Number(secret.key.charAt(0))))
|
.map((secret) => !Number.isNaN(Number(secret.key.charAt(0))))
|
||||||
.every((v) => v === false);
|
.every((v) => v === false);
|
||||||
const duplicatesExist =
|
const duplicatesExist =
|
||||||
findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0;
|
findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0;
|
||||||
|
|
||||||
if (nameErrors) {
|
if (nameErrors) {
|
||||||
setSaveLoading(false);
|
setSaveLoading(false);
|
||||||
return createNotification({
|
return createNotification({
|
||||||
text: 'Solve all name errors before saving secrets.',
|
text: 'Solve all name errors before saving secrets.',
|
||||||
type: 'error'
|
type: 'error'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duplicatesExist) {
|
if (duplicatesExist) {
|
||||||
setSaveLoading(false);
|
setSaveLoading(false);
|
||||||
return createNotification({
|
return createNotification({
|
||||||
text: 'Remove duplicated secret names before saving.',
|
text: 'Remove duplicated secret names before saving.',
|
||||||
type: 'error'
|
type: 'error'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedEnv?.isWriteDenied) {
|
if (selectedEnv?.isWriteDenied) {
|
||||||
setSaveLoading(false);
|
setSaveLoading(false);
|
||||||
return createNotification({
|
return createNotification({
|
||||||
text: 'You are not allowed to edit this environment',
|
text: 'You are not allowed to edit this environment',
|
||||||
type: 'error'
|
type: 'error'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once "Save changes" is clicked, disable that button
|
// Once "Save changes" is clicked, disable that button
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
|
|
||||||
const secretsToBeDeleted = initialData!
|
const secretsToBeDeleted = initialData!
|
||||||
.filter(
|
|
||||||
(initDataPoint) =>
|
|
||||||
!newData!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
|
|
||||||
)
|
|
||||||
.map((secret) => secret.id);
|
|
||||||
console.log('delete', secretsToBeDeleted.length);
|
|
||||||
|
|
||||||
const secretsToBeAdded = newData!.filter(
|
|
||||||
(newDataPoint) =>
|
|
||||||
!initialData!.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
|
|
||||||
);
|
|
||||||
console.log('add', secretsToBeAdded.length);
|
|
||||||
|
|
||||||
const secretsToBeUpdated = newData!.filter((newDataPoint) =>
|
|
||||||
initialData!
|
|
||||||
.filter(
|
|
||||||
(initDataPoint) =>
|
|
||||||
newData!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
|
|
||||||
(newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].value !==
|
|
||||||
initDataPoint.value ||
|
|
||||||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
|
|
||||||
initDataPoint.key ||
|
|
||||||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].comment !==
|
|
||||||
initDataPoint.comment ||
|
|
||||||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags !==
|
|
||||||
initDataPoint?.tags)
|
|
||||||
)
|
|
||||||
.map((secret) => secret.id)
|
|
||||||
.includes(newDataPoint.id)
|
|
||||||
);
|
|
||||||
console.log('update', secretsToBeUpdated.length);
|
|
||||||
|
|
||||||
const newOverrides = newData!.filter(
|
|
||||||
(newDataPoint) => newDataPoint.valueOverride !== undefined
|
|
||||||
);
|
|
||||||
const initOverrides = initialData!.filter(
|
|
||||||
(initDataPoint) => initDataPoint.valueOverride !== undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
const overridesToBeDeleted = initOverrides
|
|
||||||
.filter(
|
|
||||||
(initDataPoint) =>
|
|
||||||
!newOverrides!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
|
|
||||||
)
|
|
||||||
.map((secret) => String(secret.idOverride));
|
|
||||||
console.log('override delete', overridesToBeDeleted.length);
|
|
||||||
|
|
||||||
const overridesToBeAdded = newOverrides!
|
|
||||||
.filter(
|
|
||||||
(newDataPoint) =>
|
|
||||||
!initOverrides.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
|
|
||||||
)
|
|
||||||
.map((override) => ({
|
|
||||||
pos: override.pos,
|
|
||||||
key: override.key,
|
|
||||||
value: String(override.valueOverride),
|
|
||||||
valueOverride: override.valueOverride,
|
|
||||||
comment: '',
|
|
||||||
id: String(override.idOverride),
|
|
||||||
idOverride: String(override.idOverride),
|
|
||||||
tags: override.tags
|
|
||||||
}));
|
|
||||||
console.log('override add', overridesToBeAdded.length);
|
|
||||||
|
|
||||||
const overridesToBeUpdated = newOverrides!
|
|
||||||
.filter((newDataPoint) =>
|
|
||||||
initOverrides
|
|
||||||
.filter(
|
.filter(
|
||||||
(initDataPoint) =>
|
(initDataPoint) =>
|
||||||
newOverrides!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
|
!newData!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
|
||||||
(newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
|
|
||||||
.valueOverride !== initDataPoint.valueOverride ||
|
|
||||||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
|
|
||||||
initDataPoint.key ||
|
|
||||||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
|
|
||||||
.comment !== initDataPoint.comment ||
|
|
||||||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags !== initDataPoint?.tags)
|
|
||||||
)
|
)
|
||||||
.map((secret) => secret.id)
|
.map((secret) => secret.id);
|
||||||
.includes(newDataPoint.id)
|
console.log('delete', secretsToBeDeleted.length);
|
||||||
)
|
|
||||||
.map((override) => ({
|
|
||||||
pos: override.pos,
|
|
||||||
key: override.key,
|
|
||||||
value: String(override.valueOverride),
|
|
||||||
valueOverride: override.valueOverride,
|
|
||||||
comment: '',
|
|
||||||
id: String(override.idOverride),
|
|
||||||
idOverride: String(override.idOverride),
|
|
||||||
tags: override.tags
|
|
||||||
}));
|
|
||||||
console.log('override update', overridesToBeUpdated.length);
|
|
||||||
|
|
||||||
if (secretsToBeDeleted.concat(overridesToBeDeleted).length > 0) {
|
const secretsToBeAdded = newData!.filter(
|
||||||
await deleteSecrets({ secretIds: secretsToBeDeleted.concat(overridesToBeDeleted) });
|
(newDataPoint) =>
|
||||||
}
|
!initialData!.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
|
||||||
if (selectedEnv && secretsToBeAdded.concat(overridesToBeAdded).length > 0) {
|
);
|
||||||
const secrets = await encryptSecrets({
|
console.log('add', secretsToBeAdded.length);
|
||||||
secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded),
|
|
||||||
workspaceId,
|
|
||||||
env: selectedEnv.slug
|
|
||||||
});
|
|
||||||
if (secrets) await addSecrets({ secrets, env: selectedEnv.slug, workspaceId });
|
|
||||||
}
|
|
||||||
if (selectedEnv && !selectedEnv.isReadDenied && secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) {
|
|
||||||
const secrets = await encryptSecrets({
|
|
||||||
secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated),
|
|
||||||
workspaceId,
|
|
||||||
env: selectedEnv.slug
|
|
||||||
});
|
|
||||||
if (secrets) await updateSecrets({ secrets });
|
|
||||||
}
|
|
||||||
|
|
||||||
setInitialData(structuredClone(newData));
|
const secretsToBeUpdated = newData!.filter((newDataPoint) =>
|
||||||
|
initialData!
|
||||||
|
.filter(
|
||||||
|
(initDataPoint) =>
|
||||||
|
newData!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
|
||||||
|
(newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].value !==
|
||||||
|
initDataPoint.value ||
|
||||||
|
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
|
||||||
|
initDataPoint.key ||
|
||||||
|
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].comment !==
|
||||||
|
initDataPoint.comment ||
|
||||||
|
JSON.stringify(newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags) !==
|
||||||
|
JSON.stringify(initDataPoint?.tags))
|
||||||
|
)
|
||||||
|
.map((secret) => secret.id)
|
||||||
|
.includes(newDataPoint.id)
|
||||||
|
);
|
||||||
|
console.log('update', secretsToBeUpdated.length);
|
||||||
|
|
||||||
// If this user has never saved environment variables before, show them a prompt to read docs
|
const newOverrides = newData!.filter(
|
||||||
if (!hasUserEverPushed) {
|
(newDataPoint) => newDataPoint.valueOverride !== undefined
|
||||||
setCheckDocsPopUpVisible(true);
|
);
|
||||||
await registerUserAction({ action: 'first_time_secrets_pushed' });
|
const initOverrides = initialData!.filter(
|
||||||
}
|
(initDataPoint) => initDataPoint.valueOverride !== undefined
|
||||||
|
);
|
||||||
|
|
||||||
// increasing the number of project commits
|
const overridesToBeDeleted = initOverrides
|
||||||
setNumSnapshots((numSnapshots ?? 0) + 1);
|
.filter(
|
||||||
setSaveLoading(false);
|
(initDataPoint) =>
|
||||||
|
!newOverrides!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
|
||||||
|
)
|
||||||
|
.map((secret) => String(secret.idOverride));
|
||||||
|
console.log('override delete', overridesToBeDeleted.length);
|
||||||
|
|
||||||
|
const overridesToBeAdded = newOverrides!
|
||||||
|
.filter(
|
||||||
|
(newDataPoint) =>
|
||||||
|
!initOverrides.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
|
||||||
|
)
|
||||||
|
.map((override) => ({
|
||||||
|
pos: override.pos,
|
||||||
|
key: override.key,
|
||||||
|
value: String(override.valueOverride),
|
||||||
|
valueOverride: override.valueOverride,
|
||||||
|
comment: '',
|
||||||
|
id: String(override.idOverride),
|
||||||
|
idOverride: String(override.idOverride),
|
||||||
|
tags: override.tags
|
||||||
|
}));
|
||||||
|
console.log('override add', overridesToBeAdded.length);
|
||||||
|
|
||||||
|
const overridesToBeUpdated = newOverrides!
|
||||||
|
.filter((newDataPoint) =>
|
||||||
|
initOverrides
|
||||||
|
.filter(
|
||||||
|
(initDataPoint) =>
|
||||||
|
newOverrides!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
|
||||||
|
(newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
|
||||||
|
.valueOverride !== initDataPoint.valueOverride ||
|
||||||
|
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
|
||||||
|
initDataPoint.key ||
|
||||||
|
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
|
||||||
|
.comment !== initDataPoint.comment ||
|
||||||
|
JSON.stringify(newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags) !==
|
||||||
|
JSON.stringify(initDataPoint?.tags))
|
||||||
|
)
|
||||||
|
.map((secret) => secret.id)
|
||||||
|
.includes(newDataPoint.id)
|
||||||
|
)
|
||||||
|
.map((override) => ({
|
||||||
|
pos: override.pos,
|
||||||
|
key: override.key,
|
||||||
|
value: String(override.valueOverride),
|
||||||
|
valueOverride: override.valueOverride,
|
||||||
|
comment: '',
|
||||||
|
id: String(override.idOverride),
|
||||||
|
idOverride: String(override.idOverride),
|
||||||
|
tags: override.tags
|
||||||
|
}));
|
||||||
|
console.log('override update', overridesToBeUpdated.length);
|
||||||
|
|
||||||
|
const requests: any = []; // TODO: fix any
|
||||||
|
if (secretsToBeDeleted.concat(overridesToBeDeleted).length > 0) {
|
||||||
|
secretsToBeDeleted.concat(overridesToBeDeleted).forEach((_id: string) => {
|
||||||
|
requests.push({
|
||||||
|
method: 'DELETE',
|
||||||
|
secret: {
|
||||||
|
_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (selectedEnv && secretsToBeAdded.concat(overridesToBeAdded).length > 0) {
|
||||||
|
const secrets = await encryptSecrets({
|
||||||
|
secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded),
|
||||||
|
workspaceId,
|
||||||
|
env: selectedEnv.slug
|
||||||
|
});
|
||||||
|
if (secrets) {
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
requests.push({
|
||||||
|
method: 'POST',
|
||||||
|
secret: {
|
||||||
|
type: secret.type,
|
||||||
|
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||||
|
secretKeyIV: secret.secretKeyIV,
|
||||||
|
secretKeyTag: secret.secretKeyTag,
|
||||||
|
secretValueCiphertext: secret.secretValueCiphertext,
|
||||||
|
secretValueIV: secret.secretValueIV,
|
||||||
|
secretValueTag: secret.secretValueTag,
|
||||||
|
secretCommentCiphertext: secret.secretCommentCiphertext,
|
||||||
|
secretCommentIV: secret.secretCommentIV,
|
||||||
|
secretCommentTag: secret.secretCommentTag,
|
||||||
|
tags: secret.tags
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedEnv && !selectedEnv.isReadDenied && secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) {
|
||||||
|
const secrets = await encryptSecrets({
|
||||||
|
secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated),
|
||||||
|
workspaceId,
|
||||||
|
env: selectedEnv.slug
|
||||||
|
});
|
||||||
|
if (secrets) {
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
requests.push({
|
||||||
|
method: 'PATCH',
|
||||||
|
secret: {
|
||||||
|
_id: secret.id,
|
||||||
|
type: secret.type,
|
||||||
|
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||||
|
secretKeyIV: secret.secretKeyIV,
|
||||||
|
secretKeyTag: secret.secretKeyTag,
|
||||||
|
secretValueCiphertext: secret.secretValueCiphertext,
|
||||||
|
secretValueIV: secret.secretValueIV,
|
||||||
|
secretValueTag: secret.secretValueTag,
|
||||||
|
secretCommentCiphertext: secret.secretCommentCiphertext,
|
||||||
|
secretCommentIV: secret.secretCommentIV,
|
||||||
|
secretCommentTag: secret.secretCommentTag,
|
||||||
|
tags: secret.tags
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let newSecrets;
|
||||||
|
if (selectedEnv && requests.length > 0) {
|
||||||
|
newSecrets = await batchSecrets({
|
||||||
|
workspaceId,
|
||||||
|
environment: selectedEnv.slug,
|
||||||
|
requests
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let formattedNewDecryptedKeys;
|
||||||
|
if (newSecrets.createdSecrets) {
|
||||||
|
const latestKey = await getLatestFileKey({ workspaceId });
|
||||||
|
|
||||||
|
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
|
||||||
|
|
||||||
|
const tempDecryptedSecrets: SecretProps[] = [];
|
||||||
|
if (latestKey) {
|
||||||
|
// assymmetrically decrypt symmetric key with local private key
|
||||||
|
const key = decryptAssymmetric({
|
||||||
|
ciphertext: latestKey.latestKey.encryptedKey,
|
||||||
|
nonce: latestKey.latestKey.nonce,
|
||||||
|
publicKey: latestKey.latestKey.sender.publicKey,
|
||||||
|
privateKey: PRIVATE_KEY
|
||||||
|
});
|
||||||
|
|
||||||
|
// decrypt secret keys, values, and comments
|
||||||
|
newSecrets.createdSecrets.forEach((secret: EncryptedSecretProps) => {
|
||||||
|
const plainTextKey = decryptSymmetric({
|
||||||
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
|
iv: secret.secretKeyIV,
|
||||||
|
tag: secret.secretKeyTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
let plainTextValue;
|
||||||
|
if (secret.secretValueCiphertext !== undefined) {
|
||||||
|
plainTextValue = decryptSymmetric({
|
||||||
|
ciphertext: secret.secretValueCiphertext,
|
||||||
|
iv: secret.secretValueIV,
|
||||||
|
tag: secret.secretValueTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
plainTextValue = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let plainTextComment;
|
||||||
|
if (secret.secretCommentCiphertext) {
|
||||||
|
plainTextComment = decryptSymmetric({
|
||||||
|
ciphertext: secret.secretCommentCiphertext,
|
||||||
|
iv: secret.secretCommentIV,
|
||||||
|
tag: secret.secretCommentTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
plainTextComment = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
tempDecryptedSecrets.push({
|
||||||
|
id: secret._id,
|
||||||
|
key: plainTextKey,
|
||||||
|
value: plainTextValue,
|
||||||
|
type: secret.type,
|
||||||
|
comment: plainTextComment,
|
||||||
|
tags: secret.tags
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const secretKeys = [...new Set(tempDecryptedSecrets.map((secret) => secret.key))];
|
||||||
|
|
||||||
|
formattedNewDecryptedKeys = secretKeys.map((key, index) => ({
|
||||||
|
id: tempDecryptedSecrets.filter((secret) => secret.key === key && secret.type === 'shared')[0]
|
||||||
|
?.id,
|
||||||
|
idOverride: tempDecryptedSecrets.filter(
|
||||||
|
(secret) => secret.key === key && secret.type === 'personal'
|
||||||
|
)[0]?.id,
|
||||||
|
pos: (newData?.filter(dp => !dp.id.includes('-'))?.length ?? 0) + index,
|
||||||
|
key,
|
||||||
|
value: tempDecryptedSecrets.filter(
|
||||||
|
(secret) => secret.key === key && secret.type === 'shared'
|
||||||
|
)[0]?.value,
|
||||||
|
valueOverride: tempDecryptedSecrets.filter(
|
||||||
|
(secret) => secret.key === key && secret.type === 'personal'
|
||||||
|
)[0]?.value,
|
||||||
|
comment: tempDecryptedSecrets.filter(
|
||||||
|
(secret) => secret.key === key && secret.type === 'shared'
|
||||||
|
)[0]?.comment,
|
||||||
|
tags: tempDecryptedSecrets.filter(
|
||||||
|
(secret) => secret.key === key && secret.type === 'shared'
|
||||||
|
)[0]?.tags
|
||||||
|
}));
|
||||||
|
|
||||||
|
setInitialData(structuredClone(newData?.filter(dp => !dp.id.includes('-')).concat(formattedNewDecryptedKeys.filter(dk => dk.id))));
|
||||||
|
setData(structuredClone(newData?.filter(dp => !dp.id.includes('-')).concat(formattedNewDecryptedKeys.filter(dk => dk.id))))
|
||||||
|
} else {
|
||||||
|
setInitialData(structuredClone(newData));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this user has never saved environment variables before, show them a prompt to read docs
|
||||||
|
if (!hasUserEverPushed) {
|
||||||
|
setCheckDocsPopUpVisible(true);
|
||||||
|
await registerUserAction({ action: 'first_time_secrets_pushed' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// increasing the number of project commits
|
||||||
|
setNumSnapshots((numSnapshots ?? 0) + 1);
|
||||||
|
setSaveLoading(false);
|
||||||
|
createNotification({
|
||||||
|
text: `Successfully saved secrets.`,
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Something went wrong while saving secrets: ", error)
|
||||||
|
createNotification({
|
||||||
|
text: `Something went wrong while saving secrets.`,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -820,7 +1006,7 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-[calc(10%)] border-r border-mineshaft-600">
|
<div className="w-[calc(10%)] border-r border-mineshaft-600">
|
||||||
<div className="flex items-center max-h-16">
|
<div className="flex items-center max-h-16 overflow-hidden">
|
||||||
<div className='text-bunker-300 px-2 font-semibold h-10 flex items-center w-3/12'>Comment</div>
|
<div className='text-bunker-300 px-2 font-semibold h-10 flex items-center w-3/12'>Comment</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user