Complete first iteration of CRUD secrets operations by name

This commit is contained in:
Tuan Dang
2023-04-17 11:09:45 +03:00
parent d9afe90885
commit df83e8ceb9
29 changed files with 1231 additions and 229 deletions

View File

@@ -1,4 +1,5 @@
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import { Bot, BotKey } from '../../models';
import { createBot } from '../../helpers/bot';
@@ -29,7 +30,7 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
// -> create a new bot and return it
bot = await createBot({
name: 'Infisical Bot',
workspaceId
workspaceId: new Types.ObjectId(workspaceId)
});
}
} catch (err) {

View File

@@ -2,7 +2,7 @@ import to from 'await-to-js';
import { Types } from 'mongoose';
import { Request, Response } from 'express';
import { ISecret, Secret } from '../../models';
import { IAction } from '../../ee/models';
import { IAction, SecretVersion } from '../../ee/models';
import {
SECRET_PERSONAL,
SECRET_SHARED,
@@ -33,6 +33,7 @@ import {
* @param res
*/
export const batchSecrets = async (req: Request, res: Response) => {
const channel = getChannelFromUserAgent(req.headers['user-agent']);
const postHogClient = TelemetryService.getPostHogClient();
@@ -146,7 +147,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
await Secret.bulkWrite(updateOperations);
const secretVersions = updateSecrets.map((u) => ({
const secretVersions = updateSecrets.map((u) => new SecretVersion({
secret: new Types.ObjectId(u._id),
version: listedSecretsObj[u._id.toString()].version,
workspace: new Types.ObjectId(workspaceId),
@@ -253,7 +254,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
workspaceId: new Types.ObjectId(workspaceId)
});
const resObj: { [key: string]: ISecret[] | string[] } = {}
@@ -422,13 +423,8 @@ export const createSecrets = async (req: Request, res: Response) => {
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
tags
}) => ({
_id: new Types.ObjectId(),
secretValueTag
}) => new SecretVersion({
secret: _id,
version,
workspace,
@@ -441,11 +437,7 @@ export const createSecrets = async (req: Request, res: Response) => {
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
tags
secretValueTag
}))
});
@@ -471,7 +463,7 @@ export const createSecrets = async (req: Request, res: Response) => {
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
workspaceId: new Types.ObjectId(workspaceId)
});
const postHogClient = TelemetryService.getPostHogClient();
@@ -872,7 +864,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId: key
workspaceId: new Types.ObjectId(key)
})
const postHogClient = TelemetryService.getPostHogClient();
@@ -955,10 +947,6 @@ export const deleteSecrets = async (req: Request, res: Response) => {
}
*/
return res.status(200).send({
message: 'delete secrets!!'
});
const channel = getChannelFromUserAgent(req.headers['user-agent'])
const toDelete = req.secrets.map((s: any) => s._id);
@@ -1012,8 +1000,8 @@ export const deleteSecrets = async (req: Request, res: Response) => {
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId: key
})
workspaceId: new Types.ObjectId(key)
});
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {

View File

@@ -1,14 +1,8 @@
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import {
Secret
} from '../../models';
import crypto from 'crypto';
import { SecretService } from '../../services';
// TODO: modularize argon2id
import * as argon2 from 'argon2';
import { SecretService, TelemetryService } from '../../services';
import { getAuthDataPayloadIdObj } from '../../utils/auth';
import { BadRequestError } from '../../utils/errors';
/**
* Get secrets for workspace with id [workspaceId] and environment
@@ -17,41 +11,92 @@ import * as argon2 from 'argon2';
* @param res
*/
export const getSecrets = async (req: Request, res: Response) => {
return res.status(200).send({
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
authData: req.authData
});
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets pulled',
distinctId: TelemetryService.getDistinctId({
user: req.user,
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}),
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: req.authData.authChannel,
userAgent: req.headers?.['user-agent']
}
});
}
return res.status(200).send({
secrets
});
}
/**
* Return secret with name [secretName] for workspace with id [workspaceId]
* and environment [environment]
* Get secret with name [secretName]
* @param req
* @param res
*/
export const getSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const workspaceId = req.query.workspaceId as string;
const environment = req.query.workspaceId as string;
const environment = req.query.environment as string;
const type = req.query.type as 'shared' | 'personal' | undefined;
const secret = await SecretService.getSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData
});
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets pull',
distinctId: TelemetryService.getDistinctId({
user: req.user,
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}),
properties: {
numberOfSecrets: 1,
environment,
workspaceId,
channel: req.authData.authChannel,
userAgent: req.headers?.['user-agent']
}
});
}
return res.status(200).send({
secret
});
}
/**
* Create secret for workspace with id [workspaceId] and
* environment [environment]
* Create secret with name [secretName]
* @param req
* @param res
*/
export const createSecret = async (req: Request, res: Response) => {
// TODO: the middleware should've prevalidated that the
// workspace with id [workspaceId] has disabled E2EE
const { secretName } = req.params;
const {
workspaceId,
environment,
value,
type,
secretKeyCiphertext,
secretKeyIV,
@@ -61,66 +106,140 @@ export const createSecret = async (req: Request, res: Response) => {
secretValueTag
} = req.body;
const secretBlindIndex = await SecretService.createSecretBlindIndex({
const secret = await SecretService.createSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId)
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
});
// // use workspace salt
// const randomBytes = crypto.randomBytes(16);
// // generate blind index
// // TODO 1: abstract away into create blind index function
// // TODO 2: create a get blind index function
// const secretBlindIndex = (await argon2.hash(secretName, {
// type: argon2.argon2id,
// salt: randomBytes,
// saltLength: 16, // default 16 bytes
// memoryCost: 65536, // default pool of 64 MiB per thread.
// hashLength: 32,
// parallelism: 1,
// raw: true
// })).toString('base64');
// const secret = await new Secret({
// workspace: new Types.ObjectId(workspaceId),
// environment,
// type,
// secretBlindIndex,
// secretKeyCiphertext,
// secretKeyIV,
// secretKeyTag,
// secretValueCiphertext,
// secretValueIV,
// secretValueTag
// }).save();
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets added',
distinctId: TelemetryService.getDistinctId({
user: req.user,
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}),
properties: {
numberOfSecrets: 1,
environment,
workspaceId,
channel: req.authData.authChannel,
userAgent: req.headers?.['user-agent']
}
});
}
const secretWithoutBlindIndex = secret.toObject();
delete secretWithoutBlindIndex.secretBlindIndex;
return res.status(200).send({
secret: secretWithoutBlindIndex
});
}
/**
* Update secret with name [secretName] in workspace with id [workspaceId]
* Update secret with name [secretName]
* @param req
* @param res
*/
export const updateSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretValueCiphertext,
secretValueIV,
secretValueTag
} = req.body;
const secret = await SecretService.updateSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData,
secretValueCiphertext,
secretValueIV,
secretValueTag
});
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets modified',
distinctId: TelemetryService.getDistinctId({
user: req.user,
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}),
properties: {
numberOfSecrets: 1,
environment,
workspaceId,
channel: req.authData.authChannel,
userAgent: req.headers?.['user-agent']
}
});
}
return res.status(200).send({
secret
});
}
/**
* Delete secret with name [secretName] in workspace with id [workspaceId]
* Delete secret with name [secretName]
* @param req
* @param res
*/
export const deleteSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type
} = req.body;
const { secret, secrets } = await SecretService.deleteSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData
});
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets deleted',
distinctId: TelemetryService.getDistinctId({
user: req.user,
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}),
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: req.authData.authChannel,
userAgent: req.headers?.['user-agent']
}
});
}
return res.status(200).send({
secret
});
}

View File

@@ -207,7 +207,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
// take secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId: secret.workspace.toString()
workspaceId: secret.workspace
});
} catch (err) {

View File

@@ -304,7 +304,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
// take secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
workspaceId: new Types.ObjectId(workspaceId)
});
} catch (err) {
Sentry.setUser({ email: req.user.email });

View File

@@ -4,6 +4,7 @@ import {
Log,
IAction
} from '../models';
/**
* Create an (audit) log
* @param {Object} obj

View File

@@ -2,7 +2,7 @@ import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Secret,
ISecret
ISecret,
} from '../../models';
import {
SecretSnapshot,
@@ -21,7 +21,7 @@ import {
const takeSecretSnapshotHelper = async ({
workspaceId
}: {
workspaceId: string;
workspaceId: Types.ObjectId;
}) => {
let secretSnapshot;
@@ -143,7 +143,7 @@ const initSecretVersioningHelper = async () => {
if (unversionedSecrets.length > 0) {
await addSecretVersionsHelper({
secretVersions: unversionedSecrets.map((s, idx) => ({
secretVersions: unversionedSecrets.map((s, idx) => new SecretVersion({
...s,
secret: s._id,
version: s.version ? s.version : 1,

View File

@@ -5,6 +5,7 @@ import {
} from '../../variables';
export interface ISecretVersion {
_id: Types.ObjectId;
secret: Types.ObjectId;
version: number;
workspace: Types.ObjectId; // new
@@ -18,7 +19,6 @@ export interface ISecretVersion {
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
tags?: string[];
}
const secretVersionSchema = new Schema<ISecretVersion>(
@@ -80,12 +80,7 @@ const secretVersionSchema = new Schema<ISecretVersion>(
secretValueTag: {
type: String, // symmetric
required: true
},
tags: {
ref: 'Tag',
type: [Schema.Types.ObjectId],
default: []
},
}
},
{
timestamps: true

View File

@@ -25,7 +25,7 @@ class EESecretService {
static async takeSecretSnapshot({
workspaceId
}: {
workspaceId: string;
workspaceId: Types.ObjectId;
}) {
if (!EELicenseService.isLicenseValid) return;
return await takeSecretSnapshotHelper({ workspaceId });

View File

@@ -112,7 +112,7 @@ const createBot = async ({
workspaceId,
}: {
name: string;
workspaceId: string;
workspaceId: Types.ObjectId;
}) => {
let bot;
try {

View File

@@ -10,7 +10,8 @@ import {
EELogService
} from '../ee/services';
import {
IAction
IAction,
SecretVersion
} from '../ee/models';
import {
SECRET_SHARED,
@@ -189,8 +190,7 @@ const v1PushSecrets = async ({
secretKeyHash,
}) => {
const newSecret = newSecretsObj[`${type}-${secretKeyHash}`];
return ({
_id: new Types.ObjectId(),
return new SecretVersion({
secret: _id,
version: version ? version + 1 : 1,
workspace: new Types.ObjectId(workspaceId),
@@ -261,8 +261,7 @@ const v1PushSecrets = async ({
secretValueIV,
secretValueTag,
secretValueHash
}) => ({
_id: new Types.ObjectId(),
}) => new SecretVersion({
secret: _id,
version,
workspace,
@@ -284,7 +283,7 @@ const v1PushSecrets = async ({
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
workspaceId: new Types.ObjectId(workspaceId)
});
} catch (err) {
Sentry.setUser(null);
@@ -475,12 +474,12 @@ const v2PushSecrets = async ({
// (EE) add secret versions for new secrets
EESecretService.addSecretVersions({
secretVersions: newSecrets.map((secretDocument) => {
return {
...secretDocument.toObject(),
secretVersions: newSecrets.map((secretDocument: ISecret) => {
return new SecretVersion({
...secretDocument,
secret: secretDocument._id,
isDeleted: false
}
})
})
});
@@ -495,7 +494,7 @@ const v2PushSecrets = async ({
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
workspaceId: new Types.ObjectId(workspaceId)
})
// (EE) create (audit) log

View File

@@ -1,4 +1,11 @@
import { Types } from 'mongoose';
import {
CreateSecretParams,
GetSecretsParams,
GetSecretParams,
UpdateSecretParams,
DeleteSecretParams
} from '../interfaces/services/SecretService';
import {
User,
IUser,
@@ -10,6 +17,10 @@ import {
ISecret,
SecretBlindIndexData,
} from '../models';
import {
IAction,
SecretVersion
} from '../ee/models';
import {
validateMembership
} from '../helpers/membership';
@@ -27,17 +38,32 @@ import {
import {
BadRequestError,
UnauthorizedRequestError,
SecretNotFoundError
SecretNotFoundError,
SecretBlindIndexDataNotFoundError
} from '../utils/errors';
import {
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY
AUTH_MODE_API_KEY,
SECRET_PERSONAL,
SECRET_SHARED,
ACTION_ADD_SECRETS,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_DELETE_SECRETS
} from '../variables';
import crypto from 'crypto';
import * as argon2 from 'argon2';
import { decryptSymmetric } from '../utils/crypto';
import { getEncryptionKey } from '../config';
import {
EESecretService,
EELogService
} from '../ee/services';
import {
getAuthDataPayloadIdObj
} from '../utils/auth';
/**
* Validate authenticated clients for secrets with id [secretId] based
@@ -197,53 +223,13 @@ const validateClientForSecrets = async ({
}
/**
* Create and return blind index for secret with
* name [name] part of workspace with id [workspaceId]
* Generate blind index for secret with name [secretName]
* for workspace with id [workspaceId]
* @param {Object} obj
* @param {Object} obj.secretName - name of secret to generate blind index for
* @param {Object} obj.workspaceId - id of workspace that secret belongs to
*/
const createSecretBlindIndexHelper = async ({
secretName,
workspaceId
}: {
secretName: string;
workspaceId: Types.ObjectId;
}) => {
// check if workspace blind index data exists
// const secretBlindIndexData = await SecretBlindIndexData.findOne({
// workspace: workspaceId
// });
// if (!secretBlindIndexData) {
// // case: workspace blind index data has not been enabled
// }
// TODO: randomBytes should come from the decrypted secretBlindIndexData
const randomBytes = crypto.randomBytes(16);
const secretBlindIndex = (await argon2.hash(secretName, {
type: argon2.argon2id,
salt: randomBytes,
saltLength: 16, // default 16 bytes
memoryCost: 65536, // default pool of 64 MiB per thread.
hashLength: 32,
parallelism: 1,
raw: true
})).toString('base64');
return secretBlindIndex;
}
/**
* Return the blind index for the secret with
* name [name] part of workspace with id [workspaceId]
* @param {Object} obj
* @param {Object} obj.secretName - name of secret to generate blind index for
* @param {Object} obj.workspaceId - id of workspace that secret belongs to
*/
const getSecretBlindIndexHelper = async ({
const generateSecretBlindIndexHelper = async ({
secretName,
workspaceId
}: {
@@ -256,14 +242,505 @@ const getSecretBlindIndexHelper = async ({
workspace: workspaceId
});
if (!secretBlindIndexData) {
// case: workspace blind index data has not been enabled
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
// decrypt workspace salt
const salt = decryptSymmetric({
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
iv: secretBlindIndexData.saltIV,
tag: secretBlindIndexData.saltTag,
key: getEncryptionKey()
});
// generate secret blind index
const secretBlindIndex = (await argon2.hash(secretName, {
type: argon2.argon2id,
salt: Buffer.from(salt, 'base64'),
saltLength: 16, // default 16 bytes
memoryCost: 65536, // default pool of 64 MiB per thread.
hashLength: 32,
parallelism: 1,
raw: true
})).toString('base64');
return secretBlindIndex;
}
/**
* Create secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to create
* @param {Types.ObjectId} obj.workspaceId - id of workspace to create secret for
* @param {String} obj.environment - environment in workspace to create secret for
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
const createSecretHelper = async ({
secretName,
workspaceId,
environment,
type,
authData,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
}: CreateSecretParams) => {
// preliminary OK
// pending check for other types of clients
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
workspaceId: new Types.ObjectId(workspaceId)
});
const exists = await Secret.exists({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
type
});
if (exists) throw BadRequestError({
message: 'Failed to create secret that already exists'
});
if (type === SECRET_PERSONAL) {
// case: secret type is personal -> check if a corresponding shared secret
// with the same blind index [secretBlindIndex] exists
const exists = await Secret.exists({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
type: SECRET_SHARED
});
if (!exists) throw BadRequestError({
message: 'Failed to create personal secret override for no corresponding shared secret'
});
// TODO: adapt to other client types
if (!(authData.authPayload instanceof User)) throw BadRequestError({
message: 'Failed to create personal secret override for no specified user'
});
}
// create secret
const secret = await new Secret({
version: 1,
workspace: new Types.ObjectId(workspaceId),
environment,
type,
...(type === SECRET_PERSONAL && (authData.authPayload instanceof User) ? {
user: authData.authPayload._id
} : {}),
secretBlindIndex,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
}).save();
const secretVersion = new SecretVersion({
secret: secret._id,
version: secret.version,
workspace: secret.workspace,
type,
...(type === SECRET_PERSONAL && authData.authPayload instanceof User ? {
user: authData.authPayload._id
} : {}),
environment: secret.environment,
isDeleted: false,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
});
// // (EE) add version for new secret
await EESecretService.addSecretVersions({
secretVersions: [secretVersion]
});
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_ADD_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: [secret._id]
});
action && await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.authChannel,
ipAddress: authData.authIP
});
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
});
return secret;
}
/**
* Get secrets for workspace with id [workspaceId] and environment [environment]
* @param {Object} obj
* @param {Types.ObjectId} obj.workspaceId - id of workspace
* @param {String} obj.environment - environment in workspace
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
const getSecretsHelper = async ({
workspaceId,
environment,
authData
}: GetSecretsParams) => {
// preliminary OK
// pending check for other types of clients
let secrets: ISecret[] = [];
if (authData.authPayload instanceof User) {
// case: get personal secrets first
secrets = await Secret.find({
workspace: new Types.ObjectId(workspaceId),
environment,
type: SECRET_PERSONAL,
user: authData.authPayload._id
});
}
secrets = secrets.concat(await Secret.find({
workspace: new Types.ObjectId(workspaceId),
environment,
type: SECRET_SHARED,
secretBlindIndex: {
$nin: secrets.map((secret) => secret.secretBlindIndex)
}
}));
const action = await EELogService.createAction({
name: ACTION_READ_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: secrets.map((secret) => secret._id)
});
action && await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.authChannel,
ipAddress: authData.authIP
});
return secrets;
}
/**
* Get secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to get
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
const getSecretHelper = async ({
secretName,
workspaceId,
environment,
type,
authData
}: GetSecretParams) => {
// preliminary OK
// pending check for other types of clients
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
workspaceId: new Types.ObjectId(workspaceId)
});
let secret;
if (authData.authPayload instanceof User) {
// case: find any personal secret matching criteria
secret = await Secret.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
type: type ?? SECRET_PERSONAL,
...(type === SECRET_PERSONAL ? {
user: authData.authPayload._id
} : {})
});
if (!secret) {
// case: failed to find personal secret matching criteria
// -> find shared secret matching criteria
secret = await Secret.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
type: SECRET_SHARED
});
}
}
if (!secret) throw SecretNotFoundError();
const action = await EELogService.createAction({
name: ACTION_READ_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: [secret._id]
});
action && await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.authChannel,
ipAddress: authData.authIP
});
return secret;
}
/**
* Update secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to update
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {String} obj.secretValueCiphertext - ciphertext of secret value
* @param {String} obj.secretValueIV - IV of secret value
* @param {String} obj.secretValueTag - tag of secret value
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
const updateSecretHelper = async ({
secretName,
workspaceId,
environment,
type,
authData,
secretValueCiphertext,
secretValueIV,
secretValueTag
}: UpdateSecretParams) => {
// preliminary OK
// pending check for other types of clients
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
workspaceId: new Types.ObjectId(workspaceId)
});
let secret: ISecret | null = null;
if (type === SECRET_SHARED) {
secret = await Secret.findOneAndUpdate(
{
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
type
},
{
secretValueCiphertext,
secretValueIV,
secretValueTag,
$inc: { version: 1 }
},
{
new: true
}
);
} else if (type === SECRET_PERSONAL && authData.authPayload instanceof User) {
secret = await Secret.findOneAndUpdate(
{
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
type,
user: authData.authPayload._id
},
{
secretValueCiphertext,
secretValueIV,
secretValueTag,
$inc: { version: 1 }
},
{
new: true
}
);
}
if (!secret) throw SecretNotFoundError();
const secretVersion = new SecretVersion({
secret: secret._id,
version: secret.version,
workspace: secret.workspace,
type,
...(type === SECRET_PERSONAL && authData.authPayload instanceof User ? {
user: authData.authPayload._id
} : {}),
environment: secret.environment,
isDeleted: false,
secretKeyCiphertext: secret.secretKeyCiphertext,
secretKeyIV: secret.secretKeyIV,
secretKeyTag: secret.secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
});
// // (EE) add version for new secret
await EESecretService.addSecretVersions({
secretVersions: [secretVersion]
});
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_UPDATE_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: [secret._id]
});
action && await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.authChannel,
ipAddress: authData.authIP
});
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
});
return secret;
}
/**
* Delete secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to delete
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
const deleteSecretHelper = async ({
secretName,
workspaceId,
environment,
type,
authData
}: DeleteSecretParams) => {
// preliminary OK
// pending check for other types of clients
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
workspaceId: new Types.ObjectId(workspaceId)
});
let secrets: ISecret[] = [];
let secret: ISecret | null = null;
if (type === SECRET_SHARED) {
secrets = await Secret.find({
secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId),
environment
});
secret = await Secret.findOneAndDelete({
secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type
});
await Secret.deleteMany({
secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId),
environment
});
} else if (type === SECRET_PERSONAL && authData.authPayload instanceof User) {
secret = await Secret.findOneAndDelete({
secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
user: authData.authPayload._id
});
if (secret) {
secrets = [secret];
}
}
if (!secret) throw SecretNotFoundError();
await EESecretService.markDeletedSecretVersions({
secretIds: secrets.map((secret) => secret._id)
});
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: secrets.map((secret) => secret._id)
});
// (EE) take a secret snapshot
action && await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.authChannel,
ipAddress: authData.authIP
});
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
});
return ({
secrets,
secret
});
}
export {
validateClientForSecret,
validateClientForSecrets,
createSecretBlindIndexHelper,
getSecretBlindIndexHelper
generateSecretBlindIndexHelper,
createSecretHelper,
getSecretsHelper,
getSecretHelper,
updateSecretHelper,
deleteSecretHelper
}

View File

@@ -1,4 +1,5 @@
import * as Sentry from '@sentry/node';
import crypto from 'crypto';
import { Types } from 'mongoose';
import {
Workspace,
@@ -13,6 +14,7 @@ import {
IServiceAccount,
ServiceTokenData,
IServiceTokenData,
SecretBlindIndexData
} from '../models';
import { createBot } from '../helpers/bot';
import { validateUserClientForWorkspace } from '../helpers/user';
@@ -26,6 +28,8 @@ import {
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY
} from '../variables';
import { getEncryptionKey } from '../config';
import { encryptSymmetric } from '../utils/crypto';
/**
* Validate authenticated clients for workspace with id [workspaceId] based
@@ -42,7 +46,8 @@ const validateClientForWorkspace = async ({
workspaceId,
environment,
acceptedRoles,
requiredPermissions
requiredPermissions,
requireBlindIndicesEnabled
}: {
authData: {
authMode: string;
@@ -52,6 +57,7 @@ const validateClientForWorkspace = async ({
environment?: string;
acceptedRoles: Array<'admin' | 'member'>;
requiredPermissions?: string[];
requireBlindIndicesEnabled: boolean;
}) => {
const workspace = await Workspace.findById(workspaceId);
@@ -60,6 +66,16 @@ const validateClientForWorkspace = async ({
message: 'Failed to find workspace'
});
if (requireBlindIndicesEnabled && !workspace.isBlindedIndicesEnabled) {
// case: blind indices are not enabled for secrets in this workspace
// (i.e. workspace was created before blind indices were introduced
// and no admin has enabled it)
throw UnauthorizedRequestError({
message: 'Failed workspace authorization due to blind indices not being enabled'
});
}
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
const membership = await validateUserClientForWorkspace({
user: authData.authPayload,
@@ -130,13 +146,36 @@ const createWorkspace = async ({
// create workspace
workspace = await new Workspace({
name,
organization: organizationId
organization: organizationId,
autoCapitalization: true,
isBlindedIndicesEnabled: true
}).save();
const bot = await createBot({
// initialize bot for workspace
await createBot({
name: 'Infisical Bot',
workspaceId: workspace._id.toString()
workspaceId: workspace._id
});
// initialize blind index salt for workspace
const salt = crypto.randomBytes(16).toString('base64');
const {
ciphertext: encryptedSaltCiphertext,
iv: saltIV,
tag: saltTag
} = encryptSymmetric({
plaintext: salt,
key: getEncryptionKey()
});
await new SecretBlindIndexData({
workspace: workspace._id,
encryptedSaltCiphertext,
saltIV,
saltTag
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);

View File

@@ -0,0 +1,12 @@
import {
IUser,
IServiceAccount,
IServiceTokenData
} from '../../models';
export interface AuthData {
authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData;
authChannel: string;
authIP: string;
}

View File

@@ -0,0 +1,49 @@
import { Types } from 'mongoose';
import { AuthData } from '../../middleware';
export interface CreateSecretParams {
secretName: string;
workspaceId: Types.ObjectId;
environment: string;
type: 'shared' | 'personal';
authData: AuthData;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
}
export interface GetSecretsParams {
workspaceId: Types.ObjectId;
environment: string;
authData: AuthData;
}
export interface GetSecretParams {
secretName: string;
workspaceId: Types.ObjectId;
environment: string;
type?: 'shared' | 'personal';
authData: AuthData;
}
export interface UpdateSecretParams {
secretName: string;
workspaceId: Types.ObjectId;
environment: string;
type: 'shared' | 'personal',
authData: AuthData
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
}
export interface DeleteSecretParams {
secretName: string;
workspaceId: Types.ObjectId;
environment: string;
type: 'shared' | 'personal';
authData: AuthData;
}

View File

@@ -21,6 +21,7 @@ import {
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY
} from '../variables';
import { getChannelFromUserAgent } from '../utils/posthog';
declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -88,7 +89,9 @@ const requireAuth = ({
req.authData = {
authMode,
authPayload // User, ServiceAccount, ServiceTokenData
authPayload, // User, ServiceAccount, ServiceTokenData
authChannel: getChannelFromUserAgent(req.headers['user-agent']),
authIP: req.ip
}
return next();

View File

@@ -17,12 +17,14 @@ const requireWorkspaceAuth = ({
acceptedRoles,
locationWorkspaceId,
locationEnvironment = undefined,
requiredPermissions = []
requiredPermissions = [],
requireBlindIndicesEnabled = false
}: {
acceptedRoles: Array<'admin' | 'member'>;
locationWorkspaceId: req;
locationEnvironment?: req | undefined;
requiredPermissions?: string[];
requireBlindIndicesEnabled?: boolean;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const workspaceId = req[locationWorkspaceId]?.workspaceId;
@@ -34,7 +36,8 @@ const requireWorkspaceAuth = ({
workspaceId: new Types.ObjectId(workspaceId),
environment,
acceptedRoles,
requiredPermissions
requiredPermissions,
requireBlindIndicesEnabled
});
if (membership) {

View File

@@ -1,4 +1,4 @@
import { Schema, model, Types, Document } from 'mongoose';
import { Schema, model, Types } from 'mongoose';
import {
SECRET_SHARED,
SECRET_PERSONAL,
@@ -59,7 +59,8 @@ const secretSchema = new Schema<ISecret>(
required: true
},
secretBlindIndex: {
type: String
type: String,
select: false
},
secretKeyCiphertext: {
type: String,

View File

@@ -3,7 +3,9 @@ import { Schema, model, Types, Document } from 'mongoose';
export interface ISecretBlindIndexData {
_id: Types.ObjectId;
workspace: Types.ObjectId;
encryptedSalt: string;
encryptedSaltCiphertext: string;
saltIV: string;
saltTag: string;
}
const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
@@ -13,7 +15,15 @@ const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
ref: 'Workspace',
required: true
},
encryptedSalt: {
encryptedSaltCiphertext: {
type: String,
required: true
},
saltIV: {
type: String,
required: true
},
saltTag: {
type: String,
required: true
}

View File

@@ -1,20 +1,15 @@
import { Schema, model, Types } from 'mongoose';
import {
WORKSPACE_ENCRYPTION_MODE_E2EE,
WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE,
WORKSPACE_ENCRYPTION_MODE_NOT_E2EE
} from '../variables';
export interface IWorkspace {
_id: Types.ObjectId;
name: string;
encryptionMode: string;
organization: Types.ObjectId;
environments: Array<{
name: string;
slug: string;
}>;
autoCapitalization: boolean;
isBlindedIndicesEnabled: boolean;
}
const workspaceSchema = new Schema<IWorkspace>({
@@ -22,19 +17,14 @@ const workspaceSchema = new Schema<IWorkspace>({
type: String,
required: true
},
encryptionMode: {
type: String,
default: 'e2ee',
enum: [
WORKSPACE_ENCRYPTION_MODE_E2EE,
WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE,
WORKSPACE_ENCRYPTION_MODE_NOT_E2EE
]
},
autoCapitalization: {
type: Boolean,
default: true,
},
isBlindedIndicesEnabled: {
type: Boolean,
required: true
},
organization: {
type: Schema.Types.ObjectId,
ref: 'Organization',
@@ -70,4 +60,4 @@ const workspaceSchema = new Schema<IWorkspace>({
const Workspace = model<IWorkspace>('Workspace', workspaceSchema);
export default Workspace;
export default Workspace;

View File

@@ -1,18 +1,38 @@
import express from 'express';
const router = express.Router();
import {
requireAuth, validateRequest
requireAuth,
requireWorkspaceAuth,
validateRequest
} from '../../middleware';
import { body, param, query } from 'express-validator';
import { secretsController } from '../../controllers/v3';
// note: future endpoints pending brainstorm + implementation
import {
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
ADMIN,
MEMBER,
PERMISSION_WRITE_SECRETS,
SECRET_SHARED,
SECRET_PERSONAL,
PERMISSION_READ_SECRETS
} from '../../variables';
router.get(
'/',
query('workspaceId').exists().isString().trim(),
query('environment').exists().isString().trim(),
query('tagSlugs'),
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'query',
locationEnvironment: 'query',
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.getSecrets
);
@@ -20,6 +40,7 @@ router.post(
'/:secretName',
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(),
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body('secretKeyCiphertext').exists().isString().trim(),
body('secretKeyIV').exists().isString().trim(),
body('secretKeyTag').exists().isString().trim(),
@@ -27,6 +48,16 @@ router.post(
body('secretValueIV').exists().isString().trim(),
body('secretValueTag').exists().isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'body',
locationEnvironment: 'body',
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.createSecret
);
@@ -35,23 +66,61 @@ router.get(
param('secretName').exists().isString().trim(),
query('workspaceId').exists().isString().trim(),
query('environment').exists().isString().trim(),
query('type').optional().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'query',
locationEnvironment: 'query',
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.getSecretByName
);
router.patch(
'/:secretName',
param('secretName').exists().isString().trim(),
query('workspaceId').exists().isString().trim(),
query('environment').exists().isString().trim(),
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(),
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body('secretValueCiphertext').exists().isString().trim(),
body('secretValueIV').exists().isString().trim(),
body('secretValueTag').exists().isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'body',
locationEnvironment: 'body',
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.updateSecretByName
);
router.delete(
'/:secretName',
param('secretName').exists().isString().trim(),
query('workspaceId').exists().isString().trim(),
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(),
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'body',
locationEnvironment: 'body',
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.deleteSecretByName
);

View File

@@ -1,48 +1,187 @@
// WIP
import { Types } from 'mongoose';
import {
ISecret
} from '../models';
import {
CreateSecretParams,
GetSecretsParams,
GetSecretParams,
UpdateSecretParams,
DeleteSecretParams
} from '../interfaces/services/SecretService';
import {
createSecretBlindIndexHelper,
getSecretBlindIndexHelper
generateSecretBlindIndexHelper,
createSecretHelper,
getSecretsHelper,
getSecretHelper,
updateSecretHelper,
deleteSecretHelper
} from '../helpers/secrets';
class SecretService {
/**
* Create and return blind index for secret with
* name [name] part of workspace with id [workspaceId]
* name [secretName] part of workspace with id [workspaceId]
* @param {Object} obj
* @param {Object} obj.secretName - name of secret to generate blind index for
* @param {Object} obj.workspaceId - id of workspace that secret belongs to
*/
static async createSecretBlindIndex({
static async generateSecretBlindIndex({
secretName,
workspaceId,
}: {
secretName: string;
workspaceId: Types.ObjectId;
}) {
return await createSecretBlindIndexHelper({
return await generateSecretBlindIndexHelper({
secretName,
workspaceId
});
}
/**
* Create secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to create
* @param {Types.ObjectId} obj.workspaceId - id of workspace to create secret for
* @param {String} obj.environment - environment in workspace to create secret for
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async createSecret({
secretName,
workspaceId,
environment,
type,
authData,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
}: CreateSecretParams) {
return await createSecretHelper({
secretName,
workspaceId,
environment,
type,
authData,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
});
}
/**
* Return the blind index for the secret with
* name [name] part of workspace with id [workspaceId]
* Get secrets for workspace with id [workspaceId] and environment [environment]
* @param {Object} obj
* @param {Object} obj.secretName - name of secret to generate blind index for
* @param {Object} obj.workspaceId - id of workspace that secret belongs to
* @param {Types.ObjectId} obj.workspaceId - id of workspace
* @param {String} obj.environment - environment in workspace
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async getSecretBlindIndex({
static async getSecrets({
workspaceId,
environment,
authData
}: GetSecretsParams) {
return await getSecretsHelper({
workspaceId,
environment,
authData
});
}
/**
* Get secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to get
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async getSecret({
secretName,
workspaceId
}: {
secretName: string;
workspaceId: Types.ObjectId;
}) {
return await getSecretBlindIndexHelper({
workspaceId,
environment,
type,
authData
}: GetSecretParams) {
return await getSecretHelper({
secretName,
workspaceId
workspaceId,
environment,
type,
authData
});
}
/**
* Update secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to update
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {String} obj.secretValueCiphertext - ciphertext of secret value
* @param {String} obj.secretValueIV - IV of secret value
* @param {String} obj.secretValueTag - tag of secret value
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async updateSecret({
secretName,
workspaceId,
environment,
type,
secretValueCiphertext,
secretValueIV,
secretValueTag,
authData
}: UpdateSecretParams) {
return await updateSecretHelper({
secretName,
workspaceId,
environment,
type,
authData,
secretValueCiphertext,
secretValueIV,
secretValueTag
});
}
/**
* Delete secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to delete
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async deleteSecret({
secretName,
workspaceId,
environment,
type,
authData
}: DeleteSecretParams) {
return await deleteSecretHelper({
secretName,
workspaceId,
environment,
type,
authData
});
}
}

View File

@@ -60,6 +60,9 @@ class Telemetry {
serviceAccount?: IServiceAccount;
serviceTokenData?: any; // TODO: fix (it's ServiceTokenData with user populated)
}) => {
// TODO: modify to accept authData instead
let distinctId = '';
if (user) {

View File

@@ -5,6 +5,9 @@ import {
IServiceTokenData,
ISecret
} from '../../models';
import {
AuthData
} from '../../interfaces/middleware';
// TODO: fix (any) types
declare global {
@@ -29,10 +32,7 @@ declare global {
serviceTokenData: any;
apiKeyData: any;
query?: any;
authData: {
authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData;
};
authData: AuthData;
requestData: {
[key: string]: string
};

26
backend/src/utils/auth.ts Normal file
View File

@@ -0,0 +1,26 @@
import { AuthData } from '../interfaces/middleware';
import {
User,
ServiceAccount,
ServiceTokenData
} from '../models';
const getAuthDataPayloadIdObj = (authData: AuthData) => {
if (authData.authPayload instanceof User) {
return { userId: authData.authPayload._id };
}
if (authData.authPayload instanceof ServiceAccount) {
return { serviceAccountId: authData.authPayload._id };
}
if (authData.authPayload instanceof ServiceTokenData) {
return { serviceTokenDataId: authData.authPayload._id };
}
return {};
};
export {
getAuthDataPayloadIdObj
}

View File

@@ -153,6 +153,16 @@ export const SecretNotFoundError = (error?: Partial<RequestErrorContext>) => new
stack: error?.stack
});
//* ----->[SECRET BLIND INDEX DATA ERRORS]<-----
export const SecretBlindIndexDataNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 404,
type: error?.type ?? 'secret_blind_index_data_not_found_error',
message: error?.message ?? 'The requested secret was not found',
context: error?.context,
stack: error?.stack
});
//* ----->[SECRET SNAPSHOT ERRORS]<-----
export const SecretSnapshotNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,

View File

@@ -77,11 +77,6 @@ import {
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY
} from './authentication';
import {
WORKSPACE_ENCRYPTION_MODE_E2EE,
WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE,
WORKSPACE_ENCRYPTION_MODE_NOT_E2EE
} from './workspace';
export {
OWNER,
@@ -153,8 +148,5 @@ export {
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY,
WORKSPACE_ENCRYPTION_MODE_E2EE,
WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE,
WORKSPACE_ENCRYPTION_MODE_NOT_E2EE
AUTH_MODE_API_KEY
};

View File

@@ -1,9 +0,0 @@
const WORKSPACE_ENCRYPTION_MODE_E2EE = 'e2ee';
const WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE = 'blind-indexed-e2ee';
const WORKSPACE_ENCRYPTION_MODE_NOT_E2EE = 'not-e2ee';
export {
WORKSPACE_ENCRYPTION_MODE_E2EE,
WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE,
WORKSPACE_ENCRYPTION_MODE_NOT_E2EE
}

View File

@@ -1,8 +1,93 @@
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 (
<div>
Project encryption mode section
</div>
<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>
);
}