Skip to content

Commit

Permalink
Checkpoint service accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
dangtony98 committed Mar 18, 2023
1 parent 273f422 commit ebdcccb
Show file tree
Hide file tree
Showing 18 changed files with 403 additions and 63 deletions.
2 changes: 2 additions & 0 deletions backend/src/controllers/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as serviceTokenDataController from './serviceTokenDataController';
import * as apiKeyDataController from './apiKeyDataController';
import * as secretController from './secretController';
import * as secretsController from './secretsController';
import * as serviceAccountsController from './serviceAccountsController';
import * as environmentController from './environmentController';
import * as tagController from './tagController';

Expand All @@ -20,6 +21,7 @@ export {
apiKeyDataController,
secretController,
secretsController,
serviceAccountsController,
environmentController,
tagController
}
67 changes: 38 additions & 29 deletions backend/src/controllers/v2/organizationsController.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import {
MembershipOrg,
Membership,
Workspace
Workspace,
ServiceAccount
} from '../../models';
import { deleteMembershipOrg } from '../../helpers/membershipOrg';
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
Expand Down Expand Up @@ -260,37 +262,44 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
}
}
*/
let workspaces;
try {
const { organizationId } = req.params;
const { organizationId } = req.params;

const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId
},
'_id'
)
).map((w) => w._id.toString())
);
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId
},
'_id'
)
).map((w) => w._id.toString())
);

workspaces = (
await Membership.find({
user: req.user._id
}).populate('workspace')
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get organization workspaces'
});
}
const workspaces = (
await Membership.find({
user: req.user._id
}).populate('workspace')
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);

return res.status(200).send({
workspaces
});
}

/**
* Return service accounts for organization with id [organizationId]
* @param req
* @param res
*/
export const getOrganizationServiceAccounts = async (req: Request, res: Response) => {
const { organizationId } = req.params;
const serviceAccounts = await ServiceAccount.find({
organization: new Types.ObjectId(organizationId)
});

return res.status(200).send({
workspaces
serviceAccounts
});
}
178 changes: 178 additions & 0 deletions backend/src/controllers/v2/serviceAccountsController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import {
ServiceAccount,
ServiceAccountKey,
ServiceAccountPermission
} from '../../models';
import {
CreateServiceAccountDto
} from '../../interfaces/serviceAccounts/dto';

/**
* Create a new service account under organization with id [organizationId]
* that has access to workspaces [workspaces]
* @param req
* @param res
* @returns
*/
export const createServiceAccount = async (req: Request, res: Response) => {
const {
organizationId,
name,
publicKey,
expiresIn,
}: CreateServiceAccountDto = req.body;

let expiresAt;
if (expiresIn) {
expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
}

const serviceAccount = await new ServiceAccount({
name,
organization: new Types.ObjectId(organizationId),
user: req.user,
publicKey,
expiresAt
}).save();

// await Promise.all(
// workspaces.map(async ({
// workspaceId,
// environments,
// permissions,
// encryptedKey,
// nonce
// }: {
// workspaceId: string;
// environments: string[];
// permissions: string[];
// encryptedKey: string;
// nonce: string;
// }) => {
// const serviceAccountKey = await new ServiceAccountKey({
// encryptedKey,
// nonce,
// sender: req.user._id,
// serviceAccount: serviceAccount._id,
// workspace: new Types.ObjectId(workspaceId)
// });

// console.log('serviceAccountKey: ', serviceAccountKey);

// await Promise.all(
// permissions.map(async (name: string) => {
// const permission = await new ServiceAccountPermission({
// serviceAccount: serviceAccount._id,
// name,
// workspace: new Types.ObjectId(workspaceId),
// environments
// }).save();

// console.log('permission: ', permission);
// })
// );
// })
// );

return res.status(200).send({
serviceAccount
});
}

// /**
// * Add a service account key to service account with id [serviceAccountId]
// * for workspace with id [workspaceId]
// * @param req
// * @param res
// * @returns
// */
// export const addServiceAccountKey = async (req: Request, res: Response) => {
// const {
// workspaceId,
// encryptedKey,
// nonce
// } = req.body;

// const serviceAccountKey = await new ServiceAccountKey({
// encryptedKey,
// nonce,
// sender: req.user._id,
// serviceAccount: req.serviceAccount._d,
// workspace: new Types.ObjectId(workspaceId)
// }).save();

// return serviceAccountKey;
// }

/**
* Delete service account with id [serviceAccountId]
* @param req
* @param res
* @returns
*/
export const deleteServiceAccount = async (req: Request, res: Response) => {
const { serviceAccountId } = req.params;

const serviceAccount = await ServiceAccount.findByIdAndDelete(serviceAccountId);

await ServiceAccountKey.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId)
});

return res.status(200).send({
serviceAccount
});
}

export const addServiceAccountWorkspaceAccess = async (req: Request, res: Response) => {
const { serviceAccountId, workspaceId } = req.params;
const {
encryptedKey,
nonce,
permissions // should contain environments
} = req.body;

const serviceAccountKey = await new ServiceAccountKey({
encryptedKey,
nonce,
sender: req.user._id,
serviceAccount: req.serviceAccount._id,
workspace: new Types.ObjectId('workspaceId')
});

const serviceAccountPermissions = await Promise.all(
permissions.map
);
}

export const deleteServiceAccountWorkspaceAccess = async (req: Request, res: Response) => {
// TODO
}

// /**
// * Add a service account key to service account with id [serviceAccountId]
// * for workspace with id [workspaceId]
// * @param req
// * @param res
// * @returns
// */
// export const addServiceAccountKey = async (req: Request, res: Response) => {
// const {
// workspaceId,
// encryptedKey,
// nonce
// } = req.body;

// const serviceAccountKey = await new ServiceAccountKey({
// encryptedKey,
// nonce,
// sender: req.user._id,
// serviceAccount: req.serviceAccount._d,
// workspace: new Types.ObjectId(workspaceId)
// }).save();

// return serviceAccountKey;
// }
2 changes: 2 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
secret as v2SecretRouter, // begin to phase out
secrets as v2SecretsRouter,
serviceTokenData as v2ServiceTokenDataRouter,
serviceAccounts as v2ServiceAccountsRouter,
apiKeyData as v2APIKeyDataRouter,
environment as v2EnvironmentRouter,
tags as v2TagsRouter,
Expand Down Expand Up @@ -148,6 +149,7 @@ const main = async () => {
app.use('/api/v2/secret', v2SecretRouter); // deprecated
app.use('/api/v2/secrets', v2SecretsRouter);
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
app.use('/api/v2/service-accounts', v2ServiceAccountsRouter); // new
app.use('/api/v2/api-key', v2APIKeyDataRouter);

// api docs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface CreateServiceAccountDto {
organizationId: string;
name: string;
publicKey: string;
expiresIn: number;
}

export default CreateServiceAccountDto;
5 changes: 5 additions & 0 deletions backend/src/interfaces/serviceAccounts/dto/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import CreateServiceAccountDto from './CreateServiceAccountDto';

export {
CreateServiceAccountDto
}
2 changes: 2 additions & 0 deletions backend/src/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import requireIntegrationAuth from './requireIntegrationAuth';
import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizationAuth';
import requireServiceTokenAuth from './requireServiceTokenAuth';
import requireServiceTokenDataAuth from './requireServiceTokenDataAuth';
import requireServiceAccountAuth from './requireServiceAccountAuth';
import requireSecretAuth from './requireSecretAuth';
import requireSecretsAuth from './requireSecretsAuth';
import validateRequest from './validateRequest';
Expand All @@ -27,6 +28,7 @@ export {
requireIntegrationAuthorizationAuth,
requireServiceTokenAuth,
requireServiceTokenDataAuth,
requireServiceAccountAuth,
requireSecretAuth,
requireSecretsAuth,
validateRequest
Expand Down
10 changes: 8 additions & 2 deletions backend/src/middleware/requireOrganizationAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Request, Response, NextFunction } from 'express';
import { IOrganization, MembershipOrg } from '../models';
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';

type req = 'params' | 'body' | 'query';

/**
* Validate if user on request is a member with proper roles for organization
* on request params.
Expand All @@ -11,18 +13,22 @@ import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
*/
const requireOrganizationAuth = ({
acceptedRoles,
acceptedStatuses
acceptedStatuses,
location = 'params'
}: {
acceptedRoles: string[];
acceptedStatuses: string[];
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
// organization authorization middleware

const { organizationId } = req[location];

// validate organization membership
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: req.params.organizationId
organization: organizationId
}).populate<{ organization: IOrganization }>('organization');


Expand Down
Loading

0 comments on commit ebdcccb

Please sign in to comment.