Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#31): implemented api for environment crud operations #186

Merged
merged 8 commits into from
Jan 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
workspace as v2WorkspaceRouter,
serviceTokenData as v2ServiceTokenDataRouter,
apiKeyData as v2APIKeyDataRouter,
environment as v2EnvironmentRouter,
} from './routes/v2';

import { healthCheck } from './routes/status';
Expand Down Expand Up @@ -103,6 +104,7 @@ app.use('/api/v1/integration', v1IntegrationRouter);
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);

// v2 routes
app.use('/api/v2/workspace', v2EnvironmentRouter);
app.use('/api/v2/workspace', v2WorkspaceRouter); // TODO: turn into plural route
app.use('/api/v2/secret', v2SecretRouter); // stop supporting, TODO: revise
app.use('/api/v2/secrets', v2SecretsRouter);
Expand Down
10 changes: 8 additions & 2 deletions backend/src/controllers/v1/integrationAuthController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Sentry from '@sentry/node';
import axios from 'axios';
import { readFileSync } from 'fs';
import { IntegrationAuth, Integration } from '../../models';
import { INTEGRATION_SET, INTEGRATION_OPTIONS, ENV_DEV } from '../../variables';
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
import { IntegrationService } from '../../services';
import { getApps, revokeAccess } from '../../integrations';

Expand Down Expand Up @@ -31,11 +31,17 @@ export const oAuthExchange = async (

if (!INTEGRATION_SET.has(integration))
throw new Error('Failed to validate integration');

const environments = req.membership.workspace?.environments || [];
if(environments.length === 0){
throw new Error("Failed to get environments")
}

await IntegrationService.handleOAuthExchange({
workspaceId,
integration,
code
code,
environment: environments[0].slug,
});
} catch (err) {
Sentry.setUser(null);
Expand Down
10 changes: 6 additions & 4 deletions backend/src/controllers/v1/secretController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
import { pushKeys } from '../../helpers/key';
import { eventPushSecrets } from '../../events';
import { EventService } from '../../services';
import { ENV_SET } from '../../variables';
import { postHogClient } from '../../services';

interface PushSecret {
Expand Down Expand Up @@ -44,7 +43,8 @@ export const pushSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params;

// validate environment
if (!ENV_SET.has(environment)) {
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

Expand Down Expand Up @@ -116,7 +116,8 @@ export const pullSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params;

// validate environment
if (!ENV_SET.has(environment)) {
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

Expand Down Expand Up @@ -183,7 +184,8 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
const { workspaceId } = req.params;

// validate environment
if (!ENV_SET.has(environment)) {
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

Expand Down
4 changes: 2 additions & 2 deletions backend/src/controllers/v1/serviceTokenController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Request, Response } from 'express';
import { ServiceToken } from '../../models';
import { createToken } from '../../helpers/auth';
import { ENV_SET } from '../../variables';
import { JWT_SERVICE_SECRET } from '../../config';

/**
Expand Down Expand Up @@ -36,7 +35,8 @@ export const createServiceToken = async (req: Request, res: Response) => {
} = req.body;

// validate environment
if (!ENV_SET.has(environment)) {
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

Expand Down
204 changes: 204 additions & 0 deletions backend/src/controllers/v2/environmentController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import {
Secret,
ServiceToken,
Workspace,
Integration,
ServiceTokenData,
} from '../../models';
import { SecretVersion } from '../../ee/models';

/**
* Create new workspace environment named [environmentName] under workspace with id
* @param req
* @param res
* @returns
*/
export const createWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const { environmentName, environmentSlug } = req.body;
try {
const workspace = await Workspace.findById(workspaceId).exec();
if (
!workspace ||
workspace?.environments.find(
({ name, slug }) => slug === environmentSlug || environmentName === name
)
) {
throw new Error('Failed to create workspace environment');
}

workspace?.environments.push({
name: environmentName.toLowerCase(),
slug: environmentSlug.toLowerCase(),
});
await workspace.save();
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to create new workspace environment',
});
}

return res.status(200).send({
message: 'Successfully created new environment',
workspace: workspaceId,
environment: {
name: environmentName,
slug: environmentSlug,
},
});
};

/**
* Rename workspace environment with new name and slug of a workspace with [workspaceId]
* Old slug [oldEnvironmentSlug] must be provided
* @param req
* @param res
* @returns
*/
export const renameWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body;
try {
// user should pass both new slug and env name
if (!environmentSlug || !environmentName) {
throw new Error('Invalid environment given.');
}

// atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) {
throw new Error('Failed to create workspace environment');
}

const isEnvExist = workspace.environments.some(
({ name, slug }) =>
slug !== oldEnvironmentSlug &&
(name === environmentName || slug === environmentSlug)
);
if (isEnvExist) {
throw new Error('Invalid environment given');
}

const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === oldEnvironmentSlug
);
if (envIndex === -1) {
throw new Error('Invalid environment given');
}

workspace.environments[envIndex].name = environmentName.toLowerCase();
workspace.environments[envIndex].slug = environmentSlug.toLowerCase();

await workspace.save();
await Secret.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await SecretVersion.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await ServiceToken.updateMany(
akhilmhdh marked this conversation as resolved.
Show resolved Hide resolved
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await ServiceTokenData.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await Integration.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to update workspace environment',
});
}

return res.status(200).send({
message: 'Successfully update environment',
workspace: workspaceId,
environment: {
name: environmentName,
slug: environmentSlug,
},
});
};

/**
* Delete workspace environment by [environmentSlug] of workspace [workspaceId] and do the clean up
* @param req
* @param res
* @returns
*/
export const deleteWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const { environmentSlug } = req.body;
try {
// atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) {
throw new Error('Failed to create workspace environment');
}

const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === environmentSlug
);
if (envIndex === -1) {
throw new Error('Invalid environment given');
}

workspace.environments.splice(envIndex, 1);
await workspace.save();

// clean up
await Secret.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await SecretVersion.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await ServiceToken.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await ServiceTokenData.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await Integration.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete workspace environment',
});
}

return res.status(200).send({
message: 'Successfully deleted environment',
workspace: workspaceId,
environment: environmentSlug,
});
};
4 changes: 3 additions & 1 deletion backend/src/controllers/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import * as serviceTokenDataController from './serviceTokenDataController';
import * as apiKeyDataController from './apiKeyDataController';
import * as secretController from './secretController';
import * as secretsController from './secretsController';
import * as environmentController from './environmentController';

export {
workspaceController,
serviceTokenDataController,
apiKeyDataController,
secretController,
secretsController
secretsController,
environmentController
}
9 changes: 7 additions & 2 deletions backend/src/controllers/v2/workspaceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
import { pushKeys } from '../../helpers/key';
import { postHogClient, EventService } from '../../services';
import { eventPushSecrets } from '../../events';
import { ENV_SET } from '../../variables';

interface V2PushSecret {
type: string; // personal or shared
Expand Down Expand Up @@ -52,7 +51,8 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params;

// validate environment
if (!ENV_SET.has(environment)) {
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

Expand Down Expand Up @@ -129,6 +129,11 @@ export const pullSecrets = async (req: Request, res: Response) => {
} else if (req.serviceTokenData) {
userId = req.serviceTokenData.user._id
}
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}

secrets = await pull({
userId,
Expand Down
5 changes: 0 additions & 5 deletions backend/src/ee/models/secretVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import { Schema, model, Types } from 'mongoose';
import {
SECRET_SHARED,
SECRET_PERSONAL,
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD
} from '../../variables';

export interface ISecretVersion {
Expand Down Expand Up @@ -56,7 +52,6 @@ const secretVersionSchema = new Schema<ISecretVersion>(
},
environment: {
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true
},
isDeleted: { // consider removing field
Expand Down
Loading