mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 18:27:19 +00:00
Merge pull request #179 from Infisical/new-routing
Migrated `POST /v1/secret/:workspaceId` to `POST /v2/workspace/:workspaceId/secrets`
This commit is contained in:
+10
-2
@@ -15,7 +15,6 @@ import {
|
|||||||
workspace as eeWorkspaceRouter,
|
workspace as eeWorkspaceRouter,
|
||||||
secret as eeSecretRouter
|
secret as eeSecretRouter
|
||||||
} from './ee/routes/v1';
|
} from './ee/routes/v1';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
signup as v1SignupRouter,
|
signup as v1SignupRouter,
|
||||||
auth as v1AuthRouter,
|
auth as v1AuthRouter,
|
||||||
@@ -35,6 +34,10 @@ import {
|
|||||||
integration as v1IntegrationRouter,
|
integration as v1IntegrationRouter,
|
||||||
integrationAuth as v1IntegrationAuthRouter
|
integrationAuth as v1IntegrationAuthRouter
|
||||||
} from './routes/v1';
|
} from './routes/v1';
|
||||||
|
import {
|
||||||
|
secret as v2SecretRouter,
|
||||||
|
workspace as v2WorkspaceRouter
|
||||||
|
} from './routes/v2';
|
||||||
|
|
||||||
import { getLogger } from './utils/logger';
|
import { getLogger } from './utils/logger';
|
||||||
import { RouteNotFoundError } from './utils/errors';
|
import { RouteNotFoundError } from './utils/errors';
|
||||||
@@ -63,7 +66,7 @@ if (NODE_ENV === 'production') {
|
|||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
}
|
}
|
||||||
|
|
||||||
// (EE) routers
|
// (EE) routes
|
||||||
app.use('/api/v1/secret', eeSecretRouter);
|
app.use('/api/v1/secret', eeSecretRouter);
|
||||||
app.use('/api/v1/workspace', eeWorkspaceRouter);
|
app.use('/api/v1/workspace', eeWorkspaceRouter);
|
||||||
|
|
||||||
@@ -86,6 +89,11 @@ app.use('/api/v1/stripe', v1StripeRouter);
|
|||||||
app.use('/api/v1/integration', v1IntegrationRouter);
|
app.use('/api/v1/integration', v1IntegrationRouter);
|
||||||
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
||||||
|
|
||||||
|
// v2 routes
|
||||||
|
app.use('/api/v2/workspace', v2WorkspaceRouter);
|
||||||
|
app.use('/api/v2/secret', v2SecretRouter);
|
||||||
|
|
||||||
|
|
||||||
//* Handle unrouted requests and respond with proper error message as well as status code
|
//* Handle unrouted requests and respond with proper error message as well as status code
|
||||||
app.use((req, res, next)=>{
|
app.use((req, res, next)=>{
|
||||||
if(res.headersSent) return next();
|
if(res.headersSent) return next();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Request, Response } from 'express';
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Key, Secret } from '../../models';
|
import { Key, Secret } from '../../models';
|
||||||
import {
|
import {
|
||||||
pushSecrets as push,
|
v1PushSecrets as push,
|
||||||
pullSecrets as pull,
|
pullSecrets as pull,
|
||||||
reformatPullSecrets
|
reformatPullSecrets
|
||||||
} from '../../helpers/secret';
|
} from '../../helpers/secret';
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import * as workspaceController from './workspaceController';
|
||||||
|
|
||||||
|
export {
|
||||||
|
workspaceController
|
||||||
|
}
|
||||||
@@ -0,0 +1,565 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import {
|
||||||
|
Workspace,
|
||||||
|
Membership,
|
||||||
|
MembershipOrg,
|
||||||
|
Integration,
|
||||||
|
IntegrationAuth,
|
||||||
|
Key,
|
||||||
|
IUser,
|
||||||
|
ServiceToken,
|
||||||
|
} from '../../models';
|
||||||
|
import {
|
||||||
|
createWorkspace as create,
|
||||||
|
deleteWorkspace as deleteWork
|
||||||
|
} from '../../helpers/workspace';
|
||||||
|
import {
|
||||||
|
v2PushSecrets as push,
|
||||||
|
pullSecrets as pull,
|
||||||
|
reformatPullSecrets
|
||||||
|
} from '../../helpers/secret';
|
||||||
|
import { pushKeys } from '../../helpers/key';
|
||||||
|
import { addMemberships } from '../../helpers/membership';
|
||||||
|
import { postHogClient, EventService } from '../../services';
|
||||||
|
import { eventPushSecrets } from '../../events';
|
||||||
|
import { ADMIN, COMPLETED, GRANTED, ENV_SET } from '../../variables';
|
||||||
|
interface V2PushSecret {
|
||||||
|
type: string; // personal or shared
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretKeyHash: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretValueHash: string;
|
||||||
|
secretCommentCiphertext?: string;
|
||||||
|
secretCommentIV?: string;
|
||||||
|
secretCommentTag?: string;
|
||||||
|
secretCommentHash?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return public keys of members of workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
|
||||||
|
let publicKeys;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
publicKeys = (
|
||||||
|
await Membership.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
}).populate<{ user: IUser }>('user', 'publicKey')
|
||||||
|
)
|
||||||
|
.filter((m) => m.status === COMPLETED || m.status === GRANTED)
|
||||||
|
.map((member) => {
|
||||||
|
return {
|
||||||
|
publicKey: member.user.publicKey,
|
||||||
|
userId: member.user._id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace member public keys'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
publicKeys
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return memberships for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||||
|
let users;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
users = await Membership.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
}).populate('user', '+publicKey');
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace members'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
users
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return workspaces that user is part of
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaces = async (req: Request, res: Response) => {
|
||||||
|
let workspaces;
|
||||||
|
try {
|
||||||
|
workspaces = (
|
||||||
|
await Membership.find({
|
||||||
|
user: req.user._id
|
||||||
|
}).populate('workspace')
|
||||||
|
).map((m) => m.workspace);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspaces'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
workspaces
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspace = async (req: Request, res: Response) => {
|
||||||
|
let workspace;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
workspace = await Workspace.findOne({
|
||||||
|
_id: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
workspace
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new workspace named [workspaceName] under organization with id
|
||||||
|
* [organizationId] and add user as admin
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createWorkspace = async (req: Request, res: Response) => {
|
||||||
|
let workspace;
|
||||||
|
try {
|
||||||
|
const { workspaceName, organizationId } = req.body;
|
||||||
|
|
||||||
|
// validate organization membership
|
||||||
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
|
user: req.user._id,
|
||||||
|
organization: organizationId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!membershipOrg) {
|
||||||
|
throw new Error('Failed to validate organization membership');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspaceName.length < 1) {
|
||||||
|
throw new Error('Workspace names must be at least 1-character long');
|
||||||
|
}
|
||||||
|
|
||||||
|
// create workspace and add user as member
|
||||||
|
workspace = await create({
|
||||||
|
name: workspaceName,
|
||||||
|
organizationId
|
||||||
|
});
|
||||||
|
|
||||||
|
await addMemberships({
|
||||||
|
userIds: [req.user._id],
|
||||||
|
workspaceId: workspace._id.toString(),
|
||||||
|
roles: [ADMIN],
|
||||||
|
statuses: [GRANTED]
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to create workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
workspace
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteWorkspace = async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
// delete workspace
|
||||||
|
await deleteWork({
|
||||||
|
id: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to delete workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'Successfully deleted workspace'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change name of workspace with id [workspaceId] to [name]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const changeWorkspaceName = async (req: Request, res: Response) => {
|
||||||
|
let workspace;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
const { name } = req.body;
|
||||||
|
|
||||||
|
workspace = await Workspace.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
_id: workspaceId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to change workspace name'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'Successfully changed workspace name',
|
||||||
|
workspace
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return integrations for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
|
||||||
|
let integrations;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
integrations = await Integration.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace integrations'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
integrations
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return (integration) authorizations for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaceIntegrationAuthorizations = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
) => {
|
||||||
|
let authorizations;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
authorizations = await IntegrationAuth.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace integration authorizations'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
authorizations
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service service tokens for workspace [workspaceId] belonging to user
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaceServiceTokens = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
) => {
|
||||||
|
let serviceTokens;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
serviceTokens = await ServiceToken.find({
|
||||||
|
user: req.user._id,
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace service tokens'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceTokens
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
|
* for environment [environment]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||||
|
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
|
|
||||||
|
try {
|
||||||
|
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
||||||
|
const { keys, environment, channel } = req.body;
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
// validate environment
|
||||||
|
if (!ENV_SET.has(environment)) {
|
||||||
|
throw new Error('Failed to validate environment');
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitize secrets
|
||||||
|
secrets = secrets.filter(
|
||||||
|
(s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== ''
|
||||||
|
);
|
||||||
|
|
||||||
|
await push({
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secrets
|
||||||
|
});
|
||||||
|
|
||||||
|
await pushKeys({
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId,
|
||||||
|
keys
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: 'secrets pushed',
|
||||||
|
distinctId: req.user.email,
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel: channel ? channel : 'cli'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger event - push secrets
|
||||||
|
EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to upload workspace secrets'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'Successfully uploaded workspace secrets'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return (encrypted) secrets for workspace with id [workspaceId]
|
||||||
|
* for environment [environment] and (encrypted) workspace key
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pullSecrets = async (req: Request, res: Response) => {
|
||||||
|
// TODO: only return secrets, do not return workspace key
|
||||||
|
|
||||||
|
let secrets;
|
||||||
|
let key;
|
||||||
|
try {
|
||||||
|
const environment: string = req.query.environment as string;
|
||||||
|
const channel: string = req.query.channel as string;
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
// validate environment
|
||||||
|
if (!ENV_SET.has(environment)) {
|
||||||
|
throw new Error('Failed to validate environment');
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets = await pull({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
key = await Key.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
receiver: req.user._id
|
||||||
|
})
|
||||||
|
.sort({ createdAt: -1 })
|
||||||
|
.populate('sender', '+publicKey');
|
||||||
|
|
||||||
|
if (channel !== 'cli') {
|
||||||
|
secrets = reformatPullSecrets({ secrets });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
// capture secrets pushed event in production
|
||||||
|
postHogClient.capture({
|
||||||
|
distinctId: req.user.email,
|
||||||
|
event: 'secrets pulled',
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel: channel ? channel : 'cli'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to pull workspace secrets'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: modify based on upcoming serviceTokenData changes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return (encrypted) secrets for workspace with id [workspaceId]
|
||||||
|
* for environment [environment] and (encrypted) workspace key
|
||||||
|
* via service token
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
||||||
|
let secrets;
|
||||||
|
let key;
|
||||||
|
try {
|
||||||
|
const environment: string = req.query.environment as string;
|
||||||
|
const channel: string = req.query.channel as string;
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
// validate environment
|
||||||
|
if (!ENV_SET.has(environment)) {
|
||||||
|
throw new Error('Failed to validate environment');
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets = await pull({
|
||||||
|
userId: req.serviceToken.user._id.toString(),
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
key = {
|
||||||
|
encryptedKey: req.serviceToken.encryptedKey,
|
||||||
|
nonce: req.serviceToken.nonce,
|
||||||
|
sender: {
|
||||||
|
publicKey: req.serviceToken.publicKey
|
||||||
|
},
|
||||||
|
receiver: req.serviceToken.user,
|
||||||
|
workspace: req.serviceToken.workspace
|
||||||
|
};
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
// capture secrets pulled event in production
|
||||||
|
postHogClient.capture({
|
||||||
|
distinctId: req.serviceToken.user.email,
|
||||||
|
event: 'secrets pulled',
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel: channel ? channel : 'cli'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.serviceToken.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to pull workspace secrets'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets: reformatPullSecrets({ secrets }),
|
||||||
|
key
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
decryptSymmetric,
|
decryptSymmetric,
|
||||||
decryptAsymmetric
|
decryptAsymmetric
|
||||||
} from '../utils/crypto';
|
} from '../utils/crypto';
|
||||||
import { decryptSecrets } from '../helpers/secret';
|
|
||||||
import { ENCRYPTION_KEY } from '../config';
|
import { ENCRYPTION_KEY } from '../config';
|
||||||
import { SECRET_SHARED } from '../variables';
|
import { SECRET_SHARED } from '../variables';
|
||||||
|
|
||||||
|
|||||||
+266
-72
@@ -14,9 +14,8 @@ import {
|
|||||||
} from '../ee/helpers/secret';
|
} from '../ee/helpers/secret';
|
||||||
import { decryptSymmetric } from '../utils/crypto';
|
import { decryptSymmetric } from '../utils/crypto';
|
||||||
import { SECRET_SHARED, SECRET_PERSONAL } from '../variables';
|
import { SECRET_SHARED, SECRET_PERSONAL } from '../variables';
|
||||||
import { LICENSE_KEY } from '../config';
|
|
||||||
|
|
||||||
interface PushSecret {
|
interface V1PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
ivKey: string;
|
ivKey: string;
|
||||||
tagKey: string;
|
tagKey: string;
|
||||||
@@ -32,6 +31,22 @@ interface PushSecret {
|
|||||||
type: 'shared' | 'personal';
|
type: 'shared' | 'personal';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface V2PushSecret {
|
||||||
|
type: string; // personal or shared
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretKeyHash: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretValueHash: string;
|
||||||
|
secretCommentCiphertext?: string;
|
||||||
|
secretCommentIV?: string;
|
||||||
|
secretCommentTag?: string;
|
||||||
|
secretCommentHash?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Update {
|
interface Update {
|
||||||
[index: string]: any;
|
[index: string]: any;
|
||||||
}
|
}
|
||||||
@@ -49,7 +64,7 @@ type DecryptSecretType = 'text' | 'object' | 'expanded';
|
|||||||
* @param {String} obj.environment - environment for secrets
|
* @param {String} obj.environment - environment for secrets
|
||||||
* @param {Object[]} obj.secrets - secrets to push
|
* @param {Object[]} obj.secrets - secrets to push
|
||||||
*/
|
*/
|
||||||
const pushSecrets = async ({
|
const v1PushSecrets = async ({
|
||||||
userId,
|
userId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
@@ -58,7 +73,7 @@ const pushSecrets = async ({
|
|||||||
userId: string;
|
userId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secrets: PushSecret[];
|
secrets: V1PushSecret[];
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
// TODO: clean up function and fix up types
|
// TODO: clean up function and fix up types
|
||||||
try {
|
try {
|
||||||
@@ -99,7 +114,7 @@ const pushSecrets = async ({
|
|||||||
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
|
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
|
||||||
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue
|
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue
|
||||||
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashComment) {
|
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashComment) {
|
||||||
// case: filter secrets where value changed
|
// case: filter secrets where value or comment changed
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +274,249 @@ const pushSecrets = async ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push secrets for user with id [userId] to workspace
|
||||||
|
* with id [workspaceId] with environment [environment]. Follow steps:
|
||||||
|
* 1. Handle shared secrets (insert, delete)
|
||||||
|
* 2. handle personal secrets (insert, delete)
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.userId - id of user to push secrets for
|
||||||
|
* @param {String} obj.workspaceId - id of workspace to push to
|
||||||
|
* @param {String} obj.environment - environment for secrets
|
||||||
|
* @param {Object[]} obj.secrets - secrets to push
|
||||||
|
*/
|
||||||
|
const v2PushSecrets = async ({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secrets
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
secrets: V2PushSecret[];
|
||||||
|
}): Promise<void> => {
|
||||||
|
// TODO: clean up function and fix up types
|
||||||
|
try {
|
||||||
|
// construct useful data structures
|
||||||
|
const oldSecrets = await pullSecrets({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) =>
|
||||||
|
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s })
|
||||||
|
, {});
|
||||||
|
const newSecretsObj: any = secrets.reduce((accumulator, s) =>
|
||||||
|
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s })
|
||||||
|
, {});
|
||||||
|
|
||||||
|
// handle deleting secrets
|
||||||
|
const toDelete = oldSecrets
|
||||||
|
.filter(
|
||||||
|
(s: ISecret) => !(`${s.type}-${s.secretKeyHash}` in newSecretsObj)
|
||||||
|
)
|
||||||
|
.map((s) => s._id);
|
||||||
|
if (toDelete.length > 0) {
|
||||||
|
await Secret.deleteMany({
|
||||||
|
_id: { $in: toDelete }
|
||||||
|
});
|
||||||
|
|
||||||
|
await SecretVersion.updateMany({
|
||||||
|
secret: { $in: toDelete }
|
||||||
|
}, {
|
||||||
|
isDeleted: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const toUpdate = oldSecrets
|
||||||
|
.filter((s) => {
|
||||||
|
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
|
||||||
|
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretValueHash
|
||||||
|
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretCommentHash) {
|
||||||
|
// case: filter secrets where value or comment changed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s.version) {
|
||||||
|
// case: filter (legacy) secrets that were not versioned
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const operations = toUpdate
|
||||||
|
.map((s) => {
|
||||||
|
const {
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
} = newSecretsObj[`${s.type}-${s.secretKeyHash}`];
|
||||||
|
|
||||||
|
const update: Update = {
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s.version) {
|
||||||
|
// case: (legacy) secret was not versioned
|
||||||
|
update.version = 1;
|
||||||
|
} else {
|
||||||
|
update['$inc'] = {
|
||||||
|
version: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.type === SECRET_PERSONAL) {
|
||||||
|
// attach user associated with the personal secret
|
||||||
|
update['user'] = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateOne: {
|
||||||
|
filter: {
|
||||||
|
_id: oldSecretsObj[`${s.type}-${s.secretKeyHash}`]._id
|
||||||
|
},
|
||||||
|
update
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await Secret.bulkWrite(operations as any);
|
||||||
|
|
||||||
|
// (EE) add secret versions for updated secrets
|
||||||
|
await EESecretService.addSecretVersions({
|
||||||
|
secretVersions: toUpdate.map((s) => {
|
||||||
|
const {
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
} = newSecretsObj[`${s.type}-${s.secretKeyHash}`];
|
||||||
|
|
||||||
|
return ({
|
||||||
|
secret: s._id,
|
||||||
|
version: s.version ? s.version + 1 : 1,
|
||||||
|
isDeleted: false,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle adding new secrets
|
||||||
|
const toAdd = secrets.filter((s) => !(`${s.type}-${s.secretKeyHash}` in oldSecretsObj));
|
||||||
|
|
||||||
|
if (toAdd.length > 0) {
|
||||||
|
// add secrets
|
||||||
|
const newSecrets = await Secret.insertMany(
|
||||||
|
toAdd.map(({
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
}, idx) => {
|
||||||
|
const obj: any = {
|
||||||
|
version: 1,
|
||||||
|
workspace: workspaceId,
|
||||||
|
type: toAdd[idx].type,
|
||||||
|
environment,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash
|
||||||
|
};
|
||||||
|
|
||||||
|
if (toAdd[idx].type === 'personal') {
|
||||||
|
obj['user' as keyof typeof obj] = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// (EE) add secret versions for new secrets
|
||||||
|
EESecretService.addSecretVersions({
|
||||||
|
secretVersions: newSecrets.map(({
|
||||||
|
_id,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
}) => ({
|
||||||
|
secret: _id,
|
||||||
|
version: 1,
|
||||||
|
isDeleted: false,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// (EE) take a secret snapshot
|
||||||
|
await EESecretService.takeSecretSnapshot({
|
||||||
|
workspaceId
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to push shared and personal secrets');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pull secrets for user with id [userId] for workspace
|
* Pull secrets for user with id [userId] for workspace
|
||||||
* with id [workspaceId] with environment [environment]
|
* with id [workspaceId] with environment [environment]
|
||||||
@@ -350,73 +608,9 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
|||||||
return reformatedSecrets;
|
return reformatedSecrets;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return decrypted secrets in format [format]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object[]} obj.secrets - array of (encrypted) secret key-value pair objects
|
|
||||||
* @param {String} obj.key - symmetric key to decrypt secret key-value pairs
|
|
||||||
* @param {String} obj.format - desired return format that is either "text," "object," or "expanded"
|
|
||||||
* @return {String|Object} (decrypted) secrets also called the content
|
|
||||||
*/
|
|
||||||
const decryptSecrets = ({
|
|
||||||
secrets,
|
|
||||||
key,
|
|
||||||
format
|
|
||||||
}: {
|
|
||||||
secrets: PushSecret[];
|
|
||||||
key: string;
|
|
||||||
format: DecryptSecretType;
|
|
||||||
}) => {
|
|
||||||
// init content
|
|
||||||
let content: any = format === 'text' ? '' : {};
|
|
||||||
|
|
||||||
// decrypt secrets
|
|
||||||
secrets.forEach((s, idx) => {
|
|
||||||
const secretKey = decryptSymmetric({
|
|
||||||
ciphertext: s.ciphertextKey,
|
|
||||||
iv: s.ivKey,
|
|
||||||
tag: s.tagKey,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretValue = decryptSymmetric({
|
|
||||||
ciphertext: s.ciphertextValue,
|
|
||||||
iv: s.ivValue,
|
|
||||||
tag: s.tagValue,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case 'text':
|
|
||||||
content += secretKey;
|
|
||||||
content += '=';
|
|
||||||
content += secretValue;
|
|
||||||
|
|
||||||
if (idx < secrets.length) {
|
|
||||||
content += '\n';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'object':
|
|
||||||
content[secretKey] = secretValue;
|
|
||||||
break;
|
|
||||||
case 'expanded':
|
|
||||||
content[secretKey] = {
|
|
||||||
...s,
|
|
||||||
plaintextKey: secretKey,
|
|
||||||
plaintextValue: secretValue
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return content;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
pushSecrets,
|
v1PushSecrets,
|
||||||
|
v2PushSecrets,
|
||||||
pullSecrets,
|
pullSecrets,
|
||||||
reformatPullSecrets,
|
reformatPullSecrets
|
||||||
decryptSecrets
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import secret from './secret';
|
||||||
|
import workspace from './workspace';
|
||||||
|
|
||||||
|
export {
|
||||||
|
secret,
|
||||||
|
workspace
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import { body, param, query } from 'express-validator';
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth,
|
||||||
|
requireServiceTokenAuth,
|
||||||
|
validateRequest
|
||||||
|
} from '../../middleware';
|
||||||
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
|
import { membershipController } from '../../controllers/v1';
|
||||||
|
import { workspaceController } from '../../controllers/v2';
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/keys',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspacePublicKeys
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/users',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspaceMemberships
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get('/', requireAuth, workspaceController.getWorkspaces);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/',
|
||||||
|
requireAuth,
|
||||||
|
body('workspaceName').exists().trim().notEmpty(),
|
||||||
|
body('organizationId').exists().trim().notEmpty(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.createWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/:workspaceId',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.deleteWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:workspaceId/name',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
body('name').exists().trim().notEmpty(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.changeWorkspaceName
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:workspaceId/invite-signup',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
body('email').exists().trim().notEmpty(),
|
||||||
|
validateRequest,
|
||||||
|
membershipController.inviteUserToWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/integrations',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspaceIntegrations
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/authorizations',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspaceIntegrationAuthorizations
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get( // TODO: modify
|
||||||
|
'/:workspaceId/service-tokens',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspaceServiceTokens
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:workspaceId/secrets',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
body('secrets').exists(),
|
||||||
|
body('keys').exists(),
|
||||||
|
body('environment').exists().trim().notEmpty(),
|
||||||
|
body('channel'),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.pushWorkspaceSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/secrets',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
query('environment').exists().trim(),
|
||||||
|
query('channel'),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.pullSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get( // TODO: modify based on upcoming serviceTokenData changes
|
||||||
|
'/:workspaceId/secrets-service-token',
|
||||||
|
requireServiceTokenAuth,
|
||||||
|
query('environment').exists().trim(),
|
||||||
|
query('channel'),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.pullSecretsServiceToken
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -47,9 +47,9 @@ const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: st
|
|||||||
const secrets = Object.keys(obj).map((key) => {
|
const secrets = Object.keys(obj).map((key) => {
|
||||||
// encrypt key
|
// encrypt key
|
||||||
const {
|
const {
|
||||||
ciphertext: ciphertextKey,
|
ciphertext: secretKeyCiphertext,
|
||||||
iv: ivKey,
|
iv: secretKeyIV,
|
||||||
tag: tagKey,
|
tag: secretKeyTag,
|
||||||
} = encryptSymmetric({
|
} = encryptSymmetric({
|
||||||
plaintext: key.slice(1),
|
plaintext: key.slice(1),
|
||||||
key: randomBytes,
|
key: randomBytes,
|
||||||
@@ -57,9 +57,9 @@ const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: st
|
|||||||
|
|
||||||
// encrypt value
|
// encrypt value
|
||||||
const {
|
const {
|
||||||
ciphertext: ciphertextValue,
|
ciphertext: secretValueCiphertext,
|
||||||
iv: ivValue,
|
iv: secretValueIV,
|
||||||
tag: tagValue,
|
tag: secretValueTag,
|
||||||
} = encryptSymmetric({
|
} = encryptSymmetric({
|
||||||
plaintext: obj[key as keyof typeof obj][0],
|
plaintext: obj[key as keyof typeof obj][0],
|
||||||
key: randomBytes,
|
key: randomBytes,
|
||||||
@@ -67,9 +67,9 @@ const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: st
|
|||||||
|
|
||||||
// encrypt comment
|
// encrypt comment
|
||||||
const {
|
const {
|
||||||
ciphertext: ciphertextComment,
|
ciphertext: secretCommentCiphertext,
|
||||||
iv: ivComment,
|
iv: secretCommentIV,
|
||||||
tag: tagComment,
|
tag: secretCommentTag,
|
||||||
} = encryptSymmetric({
|
} = encryptSymmetric({
|
||||||
plaintext: obj[key as keyof typeof obj][1],
|
plaintext: obj[key as keyof typeof obj][1],
|
||||||
key: randomBytes,
|
key: randomBytes,
|
||||||
@@ -78,18 +78,18 @@ const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: st
|
|||||||
const visibility = key.charAt(0) == "p" ? "personal" : "shared";
|
const visibility = key.charAt(0) == "p" ? "personal" : "shared";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ciphertextKey,
|
secretKeyCiphertext,
|
||||||
ivKey,
|
secretKeyIV,
|
||||||
tagKey,
|
secretKeyTag,
|
||||||
hashKey: crypto.createHash("sha256").update(key.slice(1)).digest("hex"),
|
secretKeyHash: crypto.createHash("sha256").update(key.slice(1)).digest("hex"),
|
||||||
ciphertextValue,
|
secretValueCiphertext,
|
||||||
ivValue,
|
secretValueIV,
|
||||||
tagValue,
|
secretValueTag,
|
||||||
hashValue: crypto.createHash("sha256").update(obj[key as keyof typeof obj][0]).digest("hex"),
|
secretValueHash: crypto.createHash("sha256").update(obj[key as keyof typeof obj][0]).digest("hex"),
|
||||||
ciphertextComment,
|
secretCommentCiphertext,
|
||||||
ivComment,
|
secretCommentIV,
|
||||||
tagComment,
|
secretCommentTag,
|
||||||
hashComment: crypto.createHash("sha256").update(obj[key as keyof typeof obj][1]).digest("hex"),
|
secretCommentHash: crypto.createHash("sha256").update(obj[key as keyof typeof obj][1]).digest("hex"),
|
||||||
type: visibility,
|
type: visibility,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const uploadSecrets = async ({
|
|||||||
keys,
|
keys,
|
||||||
environment
|
environment
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return SecurityClient.fetchCall('/api/v1/secret/' + workspaceId, {
|
return SecurityClient.fetchCall('/api/v2/workspace/' + workspaceId + '/secrets', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
|
|||||||
Reference in New Issue
Block a user