mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 07:26:45 +00:00
Add frontend migration support for existing project to be blind-indexed
This commit is contained in:
@@ -15,7 +15,7 @@ import { 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';
|
||||||
import { TelemetryService } from '../../services';
|
import { TelemetryService, SecretService } from '../../services';
|
||||||
import { getChannelFromUserAgent } from '../../utils/posthog';
|
import { getChannelFromUserAgent } from '../../utils/posthog';
|
||||||
import { PERMISSION_WRITE_SECRETS } from '../../variables';
|
import { PERMISSION_WRITE_SECRETS } from '../../variables';
|
||||||
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
||||||
@@ -52,28 +52,46 @@ 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) => {
|
// get secret blind index salt
|
||||||
|
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
for await (const request of requests) {
|
||||||
|
let secretBlindIndex = '';
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'POST':
|
case 'POST':
|
||||||
|
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||||
|
secretName: request.secret.secretName,
|
||||||
|
salt
|
||||||
|
});
|
||||||
|
|
||||||
createSecrets.push({
|
createSecrets.push({
|
||||||
...request.secret,
|
...request.secret,
|
||||||
version: 1,
|
version: 1,
|
||||||
user: request.secret.type === SECRET_PERSONAL ? req.user : undefined,
|
user: request.secret.type === SECRET_PERSONAL ? req.user : undefined,
|
||||||
environment,
|
environment,
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
secretBlindIndex
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'PATCH':
|
case 'PATCH':
|
||||||
|
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||||
|
secretName: request.secret.secretName,
|
||||||
|
salt
|
||||||
|
});
|
||||||
|
|
||||||
updateSecrets.push({
|
updateSecrets.push({
|
||||||
...request.secret,
|
...request.secret,
|
||||||
_id: new Types.ObjectId(request.secret._id)
|
_id: new Types.ObjectId(request.secret._id),
|
||||||
|
secretBlindIndex
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'DELETE':
|
case 'DELETE':
|
||||||
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[] = [];
|
||||||
@@ -134,7 +152,10 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const updateOperations = updateSecrets.map((u) => ({
|
const updateOperations = updateSecrets.map((u) => ({
|
||||||
updateOne: {
|
updateOne: {
|
||||||
filter: { _id: new Types.ObjectId(u._id) },
|
filter: {
|
||||||
|
_id: new Types.ObjectId(u._id),
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
},
|
||||||
update: {
|
update: {
|
||||||
$inc: {
|
$inc: {
|
||||||
version: 1
|
version: 1
|
||||||
@@ -154,6 +175,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
type: listedSecretsObj[u._id.toString()].type,
|
type: listedSecretsObj[u._id.toString()].type,
|
||||||
environment,
|
environment,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
|
secretBlindIndex: u.secretBlindIndex,
|
||||||
secretKeyCiphertext: u.secretKeyCiphertext,
|
secretKeyCiphertext: u.secretKeyCiphertext,
|
||||||
secretKeyIV: u.secretKeyIV,
|
secretKeyIV: u.secretKeyIV,
|
||||||
secretKeyTag: u.secretKeyTag,
|
secretKeyTag: u.secretKeyTag,
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { Secret, Workspace, SecretBlindIndexData } from '../../models';
|
import { Secret } from '../../models';
|
||||||
import { SecretService } from'../../services';
|
import { SecretService } from'../../services';
|
||||||
import { BadRequestError } from '../../utils/errors';
|
|
||||||
import { decryptSymmetric } from '../../utils/crypto';
|
|
||||||
import { getEncryptionKey } from '../../config';
|
|
||||||
import * as argon2 from 'argon2';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether or not all secrets in workspace with id [workspaceId]
|
* Return whether or not all secrets in workspace with id [workspaceId]
|
||||||
@@ -18,12 +14,13 @@ export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response)
|
|||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
const secretsWithoutBlindIndex = await Secret.countDocuments({
|
const secretsWithoutBlindIndex = await Secret.countDocuments({
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
secretBlindIndex: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const isBlindIndexed = secretsWithoutBlindIndex === 0;
|
return res.status(200).send(secretsWithoutBlindIndex === 0);
|
||||||
|
|
||||||
return res.status(200).send(isBlindIndexed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,7 +44,6 @@ export const getWorkspaceSecrets = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
|
export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||||
|
|
||||||
interface SecretToUpdate {
|
interface SecretToUpdate {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -89,6 +85,6 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
|
|||||||
await Secret.bulkWrite(operations);
|
await Secret.bulkWrite(operations);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
operations
|
message: 'Successfully named workspace secrets'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -15,13 +15,7 @@ export const getSecretSnapshot = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
secretSnapshot = await SecretSnapshot
|
secretSnapshot = await SecretSnapshot
|
||||||
.findById(secretSnapshotId)
|
.findById(secretSnapshotId)
|
||||||
.populate({
|
.populate('secretVersions');
|
||||||
path: 'secretVersions',
|
|
||||||
populate: {
|
|
||||||
path: 'tags',
|
|
||||||
model: 'Tag',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!secretSnapshot) throw new Error('Failed to find secret snapshot');
|
if (!secretSnapshot) throw new Error('Failed to find secret snapshot');
|
||||||
|
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
|||||||
type,
|
type,
|
||||||
user,
|
user,
|
||||||
environment,
|
environment,
|
||||||
|
secretBlindIndex,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
@@ -240,6 +241,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
|||||||
type,
|
type,
|
||||||
user,
|
user,
|
||||||
environment,
|
environment,
|
||||||
|
secretBlindIndex: secretBlindIndex ?? undefined,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
@@ -265,6 +267,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
|||||||
type,
|
type,
|
||||||
user,
|
user,
|
||||||
environment,
|
environment,
|
||||||
|
secretBlindIndex,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
@@ -282,6 +285,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
|||||||
user,
|
user,
|
||||||
environment,
|
environment,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
|
secretBlindIndex: secretBlindIndex ?? undefined,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export interface ISecretVersion {
|
|||||||
user?: Types.ObjectId; // new
|
user?: Types.ObjectId; // new
|
||||||
environment: string; // new
|
environment: string; // new
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
|
secretBlindIndex?: string;
|
||||||
secretKeyCiphertext: string;
|
secretKeyCiphertext: string;
|
||||||
secretKeyIV: string;
|
secretKeyIV: string;
|
||||||
secretKeyTag: string;
|
secretKeyTag: string;
|
||||||
@@ -57,6 +58,10 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
|||||||
default: false,
|
default: false,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
secretBlindIndex: {
|
||||||
|
type: String,
|
||||||
|
select: false
|
||||||
|
},
|
||||||
secretKeyCiphertext: {
|
secretKeyCiphertext: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ router.get(
|
|||||||
'/:workspaceId/secrets/blind-index-status',
|
'/:workspaceId/secrets/blind-index-status',
|
||||||
param('workspaceId').exists().isString().trim(),
|
param('workspaceId').exists().isString().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
// requireAuth({
|
requireAuth({
|
||||||
// acceptedAuthModes: [AUTH_MODE_JWT]
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
// }),
|
}),
|
||||||
// requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
// acceptedRoles: [ADMIN],
|
acceptedRoles: [ADMIN],
|
||||||
// locationWorkspaceId: 'params',
|
locationWorkspaceId: 'params',
|
||||||
// }),
|
}),
|
||||||
workspacesController.getWorkspaceBlindIndexStatus
|
workspacesController.getWorkspaceBlindIndexStatus
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ router.post( // allow admins to name all workspace secrets (part of blind indice
|
|||||||
.withMessage('secretsToUpdate must be an array')
|
.withMessage('secretsToUpdate must be an array')
|
||||||
.customSanitizer((value) => {
|
.customSanitizer((value) => {
|
||||||
return value.map((secret: any) => ({
|
return value.map((secret: any) => ({
|
||||||
secretName: secret.name,
|
secretName: secret.secretName,
|
||||||
_id: secret._id
|
_id: secret._id
|
||||||
}));
|
}));
|
||||||
}),
|
}),
|
||||||
|
|||||||
Vendored
+1
@@ -24,6 +24,7 @@ export interface BatchSecretRequest {
|
|||||||
export interface BatchSecret {
|
export interface BatchSecret {
|
||||||
_id: string;
|
_id: string;
|
||||||
type: 'shared' | 'personal',
|
type: 'shared' | 'personal',
|
||||||
|
secretBlindIndex: string;
|
||||||
secretKeyCiphertext: string;
|
secretKeyCiphertext: string;
|
||||||
secretKeyIV: string;
|
secretKeyIV: string;
|
||||||
secretKeyTag: string;
|
secretKeyTag: string;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface EncryptedSecretProps {
|
|||||||
id: string;
|
id: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
|
secretName: string;
|
||||||
secretCommentCiphertext: string;
|
secretCommentCiphertext: string;
|
||||||
secretCommentIV: string;
|
secretCommentIV: string;
|
||||||
secretCommentTag: string;
|
secretCommentTag: string;
|
||||||
@@ -94,6 +95,7 @@ const encryptSecrets = async ({
|
|||||||
id: secret.id,
|
id: secret.id,
|
||||||
createdAt: '',
|
createdAt: '',
|
||||||
environment: env,
|
environment: env,
|
||||||
|
secretName: secret.key,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ export {
|
|||||||
useGetUserWorkspaces,
|
useGetUserWorkspaces,
|
||||||
useGetUserWsEnvironments,
|
useGetUserWsEnvironments,
|
||||||
useGetWorkspaceById,
|
useGetWorkspaceById,
|
||||||
|
useGetWorkspaceIndexStatus,
|
||||||
|
useGetWorkspaceSecrets,
|
||||||
|
useNameWorkspaceSecrets,
|
||||||
useRenameWorkspace,
|
useRenameWorkspace,
|
||||||
useToggleAutoCapitalization,
|
useToggleAutoCapitalization,
|
||||||
useUpdateWsEnvironment
|
useUpdateWsEnvironment} from './queries';
|
||||||
} from './queries';
|
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { apiRequest } from '@app/config/request';
|
import { apiRequest } from '@app/config/request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
EncryptedSecret
|
||||||
|
} from '../secrets/types';
|
||||||
import {
|
import {
|
||||||
CreateEnvironmentDTO,
|
CreateEnvironmentDTO,
|
||||||
CreateWorkspaceDTO,
|
CreateWorkspaceDTO,
|
||||||
DeleteEnvironmentDTO,
|
DeleteEnvironmentDTO,
|
||||||
DeleteWorkspaceDTO,
|
DeleteWorkspaceDTO,
|
||||||
GetWsEnvironmentDTO,
|
GetWsEnvironmentDTO,
|
||||||
|
NameWorkspaceSecretsDTO,
|
||||||
RenameWorkspaceDTO,
|
RenameWorkspaceDTO,
|
||||||
ToggleAutoCapitalizationDTO,
|
ToggleAutoCapitalizationDTO,
|
||||||
UpdateEnvironmentDTO,
|
UpdateEnvironmentDTO,
|
||||||
@@ -17,6 +21,8 @@ import {
|
|||||||
|
|
||||||
const workspaceKeys = {
|
const workspaceKeys = {
|
||||||
getWorkspaceById: (workspaceId: string) => [{ workspaceId }, 'workspace'] as const,
|
getWorkspaceById: (workspaceId: string) => [{ workspaceId }, 'workspace'] as const,
|
||||||
|
getWorkspaceSecrets: (workspaceId: string) => [{ workspaceId }, 'workspace-secrets'] as const,
|
||||||
|
getWorkspaceIndexStatus: (workspaceId: string) => [{ workspaceId}, 'workspace-index-status'] as const,
|
||||||
getWorkspaceMemberships: (orgId: string) => [{ orgId }, 'workspace-memberships'],
|
getWorkspaceMemberships: (orgId: string) => [{ orgId }, 'workspace-memberships'],
|
||||||
getAllUserWorkspace: ['workspaces'] as const,
|
getAllUserWorkspace: ['workspaces'] as const,
|
||||||
getUserWsEnvironments: (workspaceId: string) => ['workspace-env', { workspaceId }] as const
|
getUserWsEnvironments: (workspaceId: string) => ['workspace-env', { workspaceId }] as const
|
||||||
@@ -26,14 +32,47 @@ const fetchWorkspaceById = async (workspaceId: string) => {
|
|||||||
const { data } = await apiRequest.get<{ workspace: Workspace }>(
|
const { data } = await apiRequest.get<{ workspace: Workspace }>(
|
||||||
`/api/v1/workspace/${workspaceId}`
|
`/api/v1/workspace/${workspaceId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data.workspace;
|
return data.workspace;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchWorkspaceIndexStatus = async (workspaceId: string) => {
|
||||||
|
const { data } = await apiRequest.get<boolean>(
|
||||||
|
`/api/v3/workspaces/${workspaceId}/secrets/blind-index-status`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchWorkspaceSecrets = async (workspaceId: string) => {
|
||||||
|
const { data: { secrets } } = await apiRequest.get<{ secrets: EncryptedSecret[] }>(
|
||||||
|
`/api/v3/workspaces/${workspaceId}/secrets`
|
||||||
|
);
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
const fetchUserWorkspaces = async () => {
|
const fetchUserWorkspaces = async () => {
|
||||||
const { data } = await apiRequest.get<{ workspaces: Workspace[] }>('/api/v1/workspace');
|
const { data } = await apiRequest.get<{ workspaces: Workspace[] }>('/api/v1/workspace');
|
||||||
return data.workspaces;
|
return data.workspaces;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetWorkspaceIndexStatus = (workspaceId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: workspaceKeys.getWorkspaceIndexStatus(workspaceId),
|
||||||
|
queryFn: () => fetchWorkspaceIndexStatus(workspaceId),
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGetWorkspaceSecrets = (workspaceId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: workspaceKeys.getWorkspaceSecrets(workspaceId),
|
||||||
|
queryFn: () => fetchWorkspaceSecrets(workspaceId),
|
||||||
|
enabled: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const useGetWorkspaceById = (workspaceId: string) => {
|
export const useGetWorkspaceById = (workspaceId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(workspaceId),
|
queryKey: workspaceKeys.getWorkspaceById(workspaceId),
|
||||||
@@ -75,6 +114,20 @@ export const useGetUserWorkspaceMemberships = (orgId: string) =>
|
|||||||
enabled: Boolean(orgId)
|
enabled: Boolean(orgId)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const useNameWorkspaceSecrets = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<{}, {}, NameWorkspaceSecretsDTO>({
|
||||||
|
mutationFn: async ({ workspaceId, secretsToUpdate }) =>
|
||||||
|
apiRequest.post(`/api/v3/workspaces/${workspaceId}/secrets/names`, {
|
||||||
|
secretsToUpdate
|
||||||
|
}),
|
||||||
|
onSuccess: (_, variables) => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIndexStatus(variables.workspaceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// mutation
|
// mutation
|
||||||
export const useCreateWorkspace = () => {
|
export const useCreateWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ export type WorkspaceEnv = {
|
|||||||
|
|
||||||
export type WorkspaceTag = { _id: string; name: string; slug: string };
|
export type WorkspaceTag = { _id: string; name: string; slug: string };
|
||||||
|
|
||||||
|
export type NameWorkspaceSecretsDTO = {
|
||||||
|
workspaceId: string;
|
||||||
|
secretsToUpdate: {
|
||||||
|
secretName: string;
|
||||||
|
_id: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
// mutation dto
|
// mutation dto
|
||||||
export type CreateWorkspaceDTO = {
|
export type CreateWorkspaceDTO = {
|
||||||
workspaceName: string;
|
workspaceName: string;
|
||||||
|
|||||||
@@ -586,6 +586,7 @@ export default function Dashboard() {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
secret: {
|
secret: {
|
||||||
type: secret.type,
|
type: secret.type,
|
||||||
|
secretName: secret.secretName,
|
||||||
secretKeyCiphertext: secret.secretKeyCiphertext,
|
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||||
secretKeyIV: secret.secretKeyIV,
|
secretKeyIV: secret.secretKeyIV,
|
||||||
secretKeyTag: secret.secretKeyTag,
|
secretKeyTag: secret.secretKeyTag,
|
||||||
@@ -614,6 +615,7 @@ export default function Dashboard() {
|
|||||||
secret: {
|
secret: {
|
||||||
_id: secret.id,
|
_id: secret.id,
|
||||||
type: secret.type,
|
type: secret.type,
|
||||||
|
secretName: secret.secretName,
|
||||||
secretKeyCiphertext: secret.secretKeyCiphertext,
|
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||||
secretKeyIV: secret.secretKeyIV,
|
secretKeyIV: secret.secretKeyIV,
|
||||||
secretKeyTag: secret.secretKeyTag,
|
secretKeyTag: secret.secretKeyTag,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import NavHeader from '@app/components/navigation/NavHeader';
|
|||||||
// TODO(akhilmhdh):Refactor this into a better utility module package
|
// TODO(akhilmhdh):Refactor this into a better utility module package
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptSymmetric
|
decryptSymmetric,
|
||||||
} from '@app/components/utilities/cryptography/crypto';
|
encryptSymmetric} from '@app/components/utilities/cryptography/crypto';
|
||||||
import { Button, FormControl, Input } from '@app/components/v2';
|
import { Button, FormControl, Input } from '@app/components/v2';
|
||||||
import { plans } from '@app/const';
|
import { plans } from '@app/const';
|
||||||
import { useSubscription, useWorkspace } from '@app/context';
|
import { useSubscription, useWorkspace } from '@app/context';
|
||||||
@@ -25,11 +25,13 @@ import {
|
|||||||
useDeleteWsTag,
|
useDeleteWsTag,
|
||||||
useGetUserWsKey,
|
useGetUserWsKey,
|
||||||
useGetUserWsServiceTokens,
|
useGetUserWsServiceTokens,
|
||||||
|
useGetWorkspaceIndexStatus,
|
||||||
|
useGetWorkspaceSecrets,
|
||||||
useGetWsTags,
|
useGetWsTags,
|
||||||
|
useNameWorkspaceSecrets,
|
||||||
useRenameWorkspace,
|
useRenameWorkspace,
|
||||||
useToggleAutoCapitalization,
|
useToggleAutoCapitalization,
|
||||||
useUpdateWsEnvironment
|
useUpdateWsEnvironment} from '@app/hooks/api';
|
||||||
} from '@app/hooks/api';
|
|
||||||
|
|
||||||
import { AutoCapitalizationSection } from './components/AutoCapitalizationSection/AutoCapitalizationSection';
|
import { AutoCapitalizationSection } from './components/AutoCapitalizationSection/AutoCapitalizationSection';
|
||||||
import { SecretTagsSection } from './components/SecretTagsSection';
|
import { SecretTagsSection } from './components/SecretTagsSection';
|
||||||
@@ -39,9 +41,10 @@ import {
|
|||||||
CreateUpdateEnvFormData,
|
CreateUpdateEnvFormData,
|
||||||
CreateWsTag,
|
CreateWsTag,
|
||||||
EnvironmentSection,
|
EnvironmentSection,
|
||||||
ProjectEncryptionModeSection,
|
ProjectIndexSecretsSection,
|
||||||
ProjectNameChangeSection,
|
ProjectNameChangeSection,
|
||||||
ServiceTokenSection} from './components';
|
ServiceTokenSection
|
||||||
|
} from './components';
|
||||||
|
|
||||||
export const ProjectSettingsPage = () => {
|
export const ProjectSettingsPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -55,6 +58,7 @@ export const ProjectSettingsPage = () => {
|
|||||||
const [isDeleting, setIsDeleting] = useToggle();
|
const [isDeleting, setIsDeleting] = useToggle();
|
||||||
|
|
||||||
const renameWorkspace = useRenameWorkspace();
|
const renameWorkspace = useRenameWorkspace();
|
||||||
|
const nameWorkspaceSecrets = useNameWorkspaceSecrets();
|
||||||
const toggleAutoCapitalization = useToggleAutoCapitalization();
|
const toggleAutoCapitalization = useToggleAutoCapitalization();
|
||||||
|
|
||||||
const deleteWorkspace = useDeleteWorkspace();
|
const deleteWorkspace = useDeleteWorkspace();
|
||||||
@@ -63,11 +67,16 @@ export const ProjectSettingsPage = () => {
|
|||||||
const updateWsEnv = useUpdateWsEnvironment();
|
const updateWsEnv = useUpdateWsEnvironment();
|
||||||
const deleteWsEnv = useDeleteWsEnvironment();
|
const deleteWsEnv = useDeleteWsEnvironment();
|
||||||
|
|
||||||
|
const { data: isBlindIndexed, isLoading: isBlindIndexedLoading } = useGetWorkspaceIndexStatus(workspaceID);
|
||||||
|
|
||||||
// service token
|
// service token
|
||||||
const { data: serviceTokens, isLoading: isServiceTokenLoading } = useGetUserWsServiceTokens({
|
const { data: serviceTokens, isLoading: isServiceTokenLoading } = useGetUserWsServiceTokens({
|
||||||
workspaceID: currentWorkspace?._id || ''
|
workspaceID: currentWorkspace?._id || ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: latestFileKey } = useGetUserWsKey(workspaceID);
|
const { data: latestFileKey } = useGetUserWsKey(workspaceID);
|
||||||
|
const { data: encryptedSecrets } = useGetWorkspaceSecrets(workspaceID);
|
||||||
|
|
||||||
const createServiceToken = useCreateServiceToken();
|
const createServiceToken = useCreateServiceToken();
|
||||||
const deleteServiceToken = useDeleteServiceToken();
|
const deleteServiceToken = useDeleteServiceToken();
|
||||||
|
|
||||||
@@ -207,14 +216,15 @@ export const ProjectSettingsPage = () => {
|
|||||||
// type guard
|
// type guard
|
||||||
if (!latestFileKey) return '';
|
if (!latestFileKey) return '';
|
||||||
try {
|
try {
|
||||||
// crypo calculation to generate the key
|
|
||||||
const key = decryptAssymmetric({
|
const key = decryptAssymmetric({
|
||||||
ciphertext: latestFileKey.encryptedKey,
|
ciphertext: latestFileKey.encryptedKey,
|
||||||
nonce: latestFileKey.nonce,
|
nonce: latestFileKey.nonce,
|
||||||
publicKey: latestFileKey.sender.publicKey,
|
publicKey: latestFileKey.sender.publicKey,
|
||||||
privateKey: localStorage.getItem('PRIVATE_KEY') as string
|
privateKey: localStorage.getItem('PRIVATE_KEY') as string
|
||||||
});
|
});
|
||||||
|
|
||||||
const randomBytes = crypto.randomBytes(16).toString('hex');
|
const randomBytes = crypto.randomBytes(16).toString('hex');
|
||||||
|
|
||||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
const { ciphertext, iv, tag } = encryptSymmetric({
|
||||||
plaintext: key,
|
plaintext: key,
|
||||||
key: randomBytes
|
key: randomBytes
|
||||||
@@ -303,6 +313,38 @@ export const ProjectSettingsPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onEnableBlindIndices = async () => {
|
||||||
|
if (!currentWorkspace?._id) return;
|
||||||
|
if (!encryptedSecrets) return;
|
||||||
|
if (!latestFileKey) return;
|
||||||
|
|
||||||
|
const key = decryptAssymmetric({
|
||||||
|
ciphertext: latestFileKey.encryptedKey,
|
||||||
|
nonce: latestFileKey.nonce,
|
||||||
|
publicKey: latestFileKey.sender.publicKey,
|
||||||
|
privateKey: localStorage.getItem('PRIVATE_KEY') as string
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretsToUpdate = encryptedSecrets.map((encryptedSecret) => {
|
||||||
|
const secretName = decryptSymmetric({
|
||||||
|
ciphertext: encryptedSecret.secretKeyCiphertext,
|
||||||
|
iv: encryptedSecret.secretKeyIV,
|
||||||
|
tag: encryptedSecret.secretKeyTag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({
|
||||||
|
secretName,
|
||||||
|
_id: encryptedSecret._id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await nameWorkspaceSecrets.mutateAsync({
|
||||||
|
workspaceId: currentWorkspace._id,
|
||||||
|
secretsToUpdate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dark container mx-auto flex flex-col px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="dark container mx-auto flex flex-col px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
{/* TODO(akhilmhdh): Remove this right when layout is refactored */}
|
{/* TODO(akhilmhdh): Remove this right when layout is refactored */}
|
||||||
@@ -349,7 +391,11 @@ export const ProjectSettingsPage = () => {
|
|||||||
workspaceAutoCapitalization={currentWorkspace?.autoCapitalization}
|
workspaceAutoCapitalization={currentWorkspace?.autoCapitalization}
|
||||||
onAutoCapitalizationChange={onAutoCapitalizationToggle}
|
onAutoCapitalizationChange={onAutoCapitalizationToggle}
|
||||||
/>
|
/>
|
||||||
<ProjectEncryptionModeSection />
|
{!isBlindIndexedLoading && !isBlindIndexed && (
|
||||||
|
<ProjectIndexSecretsSection
|
||||||
|
onEnableBlindIndices={onEnableBlindIndices}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md border-l border-red bg-white/5 px-6 pl-6 pb-4 pt-4">
|
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md border-l border-red bg-white/5 px-6 pl-6 pb-4 pt-4">
|
||||||
<p className="text-xl font-bold text-red">{t('settings-project:danger-zone')}</p>
|
<p className="text-xl font-bold text-red">{t('settings-project:danger-zone')}</p>
|
||||||
<p className="text-md mt-2 text-gray-400">{t('settings-project:danger-zone-note')}</p>
|
<p className="text-md mt-2 text-gray-400">{t('settings-project:danger-zone-note')}</p>
|
||||||
|
|||||||
-93
@@ -1,93 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import {
|
|
||||||
Controller,
|
|
||||||
useForm
|
|
||||||
} from 'react-hook-form';
|
|
||||||
import { faCheck } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
|
||||||
import * as yup from 'yup';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
FormControl,
|
|
||||||
Select,
|
|
||||||
SelectItem
|
|
||||||
} from '@app/components/v2';
|
|
||||||
|
|
||||||
// TODO: modify in accordance with what was discussed with
|
|
||||||
// Maidul
|
|
||||||
|
|
||||||
// Do with select for now them replace.
|
|
||||||
|
|
||||||
const items = [
|
|
||||||
{ value: 'e2ee', label: 'E2EE' },
|
|
||||||
{ value: 'blind-indexed-e2ee', label: 'Blind Indexed E2EE' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const formSchema = yup.object({
|
|
||||||
mode: yup.string().required().label('Project Mode')
|
|
||||||
});
|
|
||||||
|
|
||||||
type FormData = yup.InferType<typeof formSchema>;
|
|
||||||
|
|
||||||
export const ProjectEncryptionModeSection = () => {
|
|
||||||
const {
|
|
||||||
handleSubmit,
|
|
||||||
control,
|
|
||||||
reset,
|
|
||||||
formState: { isDirty, isSubmitting }
|
|
||||||
} = useForm<FormData>({ resolver: yupResolver(formSchema) });
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
reset({ mode: 'blind-indexed-e2ee' });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onFormSubmit = async ({ mode }: FormData) => {
|
|
||||||
console.log('onFormSubmit');
|
|
||||||
console.log('mode: ', mode);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form
|
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
|
||||||
className="rounded-md bg-white/5 p-6"
|
|
||||||
>
|
|
||||||
<p className="mb-4 text-xl font-semibold">Encryption Mode</p>
|
|
||||||
<div className="mb-6 max-w-lg">
|
|
||||||
<Controller
|
|
||||||
defaultValue=""
|
|
||||||
render={({ field, fieldState: { error } }) => {
|
|
||||||
console.log('field: ', field);
|
|
||||||
return (
|
|
||||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
|
||||||
<Select
|
|
||||||
{...field}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
{items.map(item => (
|
|
||||||
<SelectItem value={item.value} key={`enc-mode-${item.value}`}>
|
|
||||||
{item.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
control={control}
|
|
||||||
name="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
isLoading={isSubmitting}
|
|
||||||
color="mineshaft"
|
|
||||||
size="sm"
|
|
||||||
type="submit"
|
|
||||||
isDisabled={!isDirty || isSubmitting}
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faCheck} />}
|
|
||||||
>
|
|
||||||
Save Changes
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
export { ProjectEncryptionModeSection } from './ProjectEncryptionModeSection';
|
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
import { Button } from '@app/components/v2';
|
||||||
|
|
||||||
|
// TODO: add check so that this only shows up if user is
|
||||||
|
// an admin in the workspace
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onEnableBlindIndices: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectIndexSecretsSection = ({
|
||||||
|
onEnableBlindIndices
|
||||||
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<div className="rounded-md bg-white/5 p-6">
|
||||||
|
<p className="mb-4 text-xl font-semibold">Blind Indices</p>
|
||||||
|
<p className="mb-4 text-sm text-gray-400">
|
||||||
|
Your project, created before the introduction of blind indexing, contains unindexed secrets. To access individual secrets by name through the SDK and public API, please enable blind indexing.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4 text-sm text-gray-400">
|
||||||
|
Learn more about it here.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={onEnableBlindIndices}
|
||||||
|
color="mineshaft"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Enable Blind Indexing
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { ProjectIndexSecretsSection } from './ProjectIndexSecretsSection';
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
export { CopyProjectIDSection } from './CopyProjectIDSection';
|
export { CopyProjectIDSection } from './CopyProjectIDSection';
|
||||||
export { EnvironmentSection } from './EnvironmentSection';
|
export { EnvironmentSection } from './EnvironmentSection';
|
||||||
export type { CreateUpdateEnvFormData } from './EnvironmentSection/EnvironmentSection';
|
export type { CreateUpdateEnvFormData } from './EnvironmentSection/EnvironmentSection';
|
||||||
export { ProjectEncryptionModeSection } from './ProjectEncryptionModeSection';
|
export { ProjectIndexSecretsSection } from './ProjectIndexSecretsSection';
|
||||||
export { ProjectNameChangeSection } from './ProjectNameChangeSection';
|
export { ProjectNameChangeSection } from './ProjectNameChangeSection';
|
||||||
export type { CreateWsTag } from './SecretTagsSection/SecretTagsSection';
|
export type { CreateWsTag } from './SecretTagsSection/SecretTagsSection';
|
||||||
export { ServiceTokenSection } from './ServiceTokenSection';
|
export { ServiceTokenSection } from './ServiceTokenSection';
|
||||||
|
|||||||
Reference in New Issue
Block a user