Clean up and weave crypto into service account permissions

This commit is contained in:
Tuan Dang
2023-03-29 18:07:15 +07:00
parent 729aacc154
commit 88c0a46de3
20 changed files with 360 additions and 240 deletions
@@ -5,8 +5,8 @@ import bcrypt from 'bcrypt';
import { import {
ServiceAccount, ServiceAccount,
ServiceAccountKey, ServiceAccountKey,
ServiceAccountOrganizationPermissions, ServiceAccountOrganizationPermission,
ServiceAccountWorkspacePermissions ServiceAccountWorkspacePermission
} from '../../models'; } from '../../models';
import { import {
CreateServiceAccountDto CreateServiceAccountDto
@@ -71,8 +71,8 @@ export const createServiceAccount = async (req: Request, res: Response) => {
delete serviceAccountObj.secretHash; delete serviceAccountObj.secretHash;
// provision default org-level permissions for service account // provision default org-level permission for service account
const permissions = await new ServiceAccountOrganizationPermissions({ await new ServiceAccountOrganizationPermission({
serviceAccount: serviceAccount._id serviceAccount: serviceAccount._id
}).save(); }).save();
@@ -136,57 +136,57 @@ export const addServiceAccountKey = async (req: Request, res: Response) => {
return serviceAccountKey; return serviceAccountKey;
} }
/** // /**
* Return organization-level permissions for service account with id [serviceAccountId] // * Return organization-level permissions for service account with id [serviceAccountId]
* @param req // * @param req
* @param res // * @param res
*/ // */
export const getServiceAccountOrganizationPermissions = async (req: Request, res: Response) => { // export const getServiceAccountOrganizationPermissions = async (req: Request, res: Response) => {
const { serviceAccountId } = req.params; // const { serviceAccountId } = req.params;
const permissions = await ServiceAccountOrganizationPermissions.findOne({ // const permissions = await ServiceAccountOrganizationPermissions.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId), // serviceAccount: new Types.ObjectId(serviceAccountId),
}); // });
return res.status(200).send({ // return res.status(200).send({
permissions // permissions
}); // });
} // }
/** /**
* Return workspace-level permissions for service account with id [serviceAccountId] * Return workspace-level permission for service account with id [serviceAccountId]
* @param req * @param req
* @param res * @param res
*/ */
export const getServiceAccountWorkspacePermissions = async (req: Request, res: Response) => { export const getServiceAccountWorkspacePermissions = async (req: Request, res: Response) => {
const permissions = await ServiceAccountWorkspacePermissions.find({ const serviceAccountWorkspacePermissions = await ServiceAccountWorkspacePermission.find({
serviceAccount: req.serviceAccount._id serviceAccount: req.serviceAccount._id
}).populate('workspace'); }).populate('workspace');
return res.status(200).send({ return res.status(200).send({
permissions serviceAccountWorkspacePermissions
}); });
} }
/** // /**
* Add organization permissions to service account with id [serviceAccountId] // * Add organization permissions to service account with id [serviceAccountId]
* @param req // * @param req
* @param res // * @param res
*/ // */
export const addServiceAccountOrganizationPermission = async (req: Request, res: Response) => { // export const addServiceAccountOrganizationPermission = async (req: Request, res: Response) => {
const permissions = ServiceAccountOrganizationPermissions.findOne({ // const permissions = ServiceAccountOrganizationPermissions.findOne({
serviceAccount: req.serviceAccount._id // serviceAccount: req.serviceAccount._id
}); // });
// TODO // // TODO
return res.status(200).send({ // return res.status(200).send({
permissions // permissions
}); // });
} // }
/** /**
* Add a workspace permissions to service account with id [serviceAccountId] * Add a workspace permission to service account with id [serviceAccountId]
* @param req * @param req
* @param res * @param res
*/ */
@@ -198,7 +198,9 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
canRead = false, canRead = false,
canWrite = false, canWrite = false,
canUpdate = false, canUpdate = false,
canDelete = false canDelete = false,
encryptedKey,
nonce
} = req.body; } = req.body;
if (!req.membership.workspace.environments.some((e: { name: string; slug: string }) => e.slug === environment)) { if (!req.membership.workspace.environments.some((e: { name: string; slug: string }) => e.slug === environment)) {
@@ -207,7 +209,7 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
}); });
} }
const existingPermission = await ServiceAccountWorkspacePermissions.findOne({ const existingPermission = await ServiceAccountWorkspacePermission.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId), serviceAccount: new Types.ObjectId(serviceAccountId),
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment environment
@@ -215,7 +217,7 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
if (existingPermission) throw BadRequestError({ message: 'Failed to add workspace permission to service account due to already-existing ' }); if (existingPermission) throw BadRequestError({ message: 'Failed to add workspace permission to service account due to already-existing ' });
const permissions = await new ServiceAccountWorkspacePermissions({ const serviceAccountWorkspacePermission = await new ServiceAccountWorkspacePermission({
serviceAccount: new Types.ObjectId(serviceAccountId), serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
environment, environment,
@@ -225,23 +227,52 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
canDelete canDelete
}).save(); }).save();
const existingServiceAccountKey = await ServiceAccountKey.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId)
});
if (!existingServiceAccountKey) {
await new ServiceAccountKey({
encryptedKey,
nonce,
sender: req.user._id,
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId)
}).save();
}
return res.status(200).send({ return res.status(200).send({
permissions serviceAccountWorkspacePermission
}); });
} }
/** /**
* Delete workspace permissions from service account with id [serviceAccountId] * Delete workspace permission from service account with id [serviceAccountId]
* @param req * @param req
* @param res * @param res
*/ */
export const deleteServiceAccountWorkspacePermission = async (req: Request, res: Response) => { export const deleteServiceAccountWorkspacePermission = async (req: Request, res: Response) => {
const { serviceAccountWorkspacePermissionsId } = req.params; const { serviceAccountWorkspacePermissionId } = req.params;
const serviceAccountWorkspacePermission = await ServiceAccountWorkspacePermission.findByIdAndDelete(serviceAccountWorkspacePermissionId);
const permissions = await ServiceAccountWorkspacePermissions.findByIdAndDelete(serviceAccountWorkspacePermissionsId);
if (serviceAccountWorkspacePermission) {
const { serviceAccount, workspace } = serviceAccountWorkspacePermission;
const count = await ServiceAccountWorkspacePermission.countDocuments({
serviceAccount,
workspace
});
if (count === 0) {
await ServiceAccountKey.findOneAndDelete({
serviceAccount,
workspace
});
}
}
return res.status(200).send({ return res.status(200).send({
permissions serviceAccountWorkspacePermission
}); });
} }
@@ -257,17 +288,15 @@ export const deleteServiceAccount = async (req: Request, res: Response) => {
const serviceAccount = await ServiceAccount.findByIdAndDelete(serviceAccountId); const serviceAccount = await ServiceAccount.findByIdAndDelete(serviceAccountId);
if (serviceAccount) { if (serviceAccount) {
// case: service account with id [serviceAccountId] was deleted
await ServiceAccountKey.deleteMany({ await ServiceAccountKey.deleteMany({
serviceAccount: serviceAccount._id serviceAccount: serviceAccount._id
}); });
await ServiceAccountOrganizationPermissions.deleteMany({ await ServiceAccountOrganizationPermission.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId) serviceAccount: new Types.ObjectId(serviceAccountId)
}); });
await ServiceAccountWorkspacePermissions.deleteMany({ await ServiceAccountWorkspacePermission.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId) serviceAccount: new Types.ObjectId(serviceAccountId)
}); });
} }
+2 -2
View File
@@ -11,7 +11,7 @@ import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizati
import requireServiceTokenAuth from './requireServiceTokenAuth'; import requireServiceTokenAuth from './requireServiceTokenAuth';
import requireServiceTokenDataAuth from './requireServiceTokenDataAuth'; import requireServiceTokenDataAuth from './requireServiceTokenDataAuth';
import requireServiceAccountAuth from './requireServiceAccountAuth'; import requireServiceAccountAuth from './requireServiceAccountAuth';
import requireServiceAccountWorkspacePermissionsAuth from './requireServiceAccountWorkspacePermissionsAuth'; import requireServiceAccountWorkspacePermissionAuth from './requireServiceAccountWorkspacePermissionAuth';
import requireSecretAuth from './requireSecretAuth'; import requireSecretAuth from './requireSecretAuth';
import requireSecretsAuth from './requireSecretsAuth'; import requireSecretsAuth from './requireSecretsAuth';
import validateRequest from './validateRequest'; import validateRequest from './validateRequest';
@@ -30,7 +30,7 @@ export {
requireServiceTokenAuth, requireServiceTokenAuth,
requireServiceTokenDataAuth, requireServiceTokenDataAuth,
requireServiceAccountAuth, requireServiceAccountAuth,
requireServiceAccountWorkspacePermissionsAuth, requireServiceAccountWorkspacePermissionAuth,
requireSecretAuth, requireSecretAuth,
requireSecretsAuth, requireSecretsAuth,
validateRequest validateRequest
@@ -1,5 +1,5 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { ServiceAccount, ServiceAccountWorkspacePermissions } from '../models'; import { ServiceAccount, ServiceAccountWorkspacePermission } from '../models';
import { import {
ServiceAccountNotFoundError ServiceAccountNotFoundError
} from '../utils/errors'; } from '../utils/errors';
@@ -9,7 +9,7 @@ import {
type req = 'params' | 'body' | 'query'; type req = 'params' | 'body' | 'query';
const requireServiceAccountWorkspacePermissionsAuth = ({ const requireServiceAccountWorkspacePermissionAuth = ({
acceptedRoles, acceptedRoles,
acceptedStatuses, acceptedStatuses,
location = 'params' location = 'params'
@@ -19,14 +19,14 @@ const requireServiceAccountWorkspacePermissionsAuth = ({
location?: req; location?: req;
}) => { }) => {
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
const serviceAccountWorkspacePermissionsId = req[location].serviceAccountWorkspacePermissionsId; const serviceAccountWorkspacePermissionId = req[location].serviceAccountWorkspacePermissionId;
const serviceAccountWorkspacePermissions = await ServiceAccountWorkspacePermissions.findById(serviceAccountWorkspacePermissionsId); const serviceAccountWorkspacePermission = await ServiceAccountWorkspacePermission.findById(serviceAccountWorkspacePermissionId);
if (!serviceAccountWorkspacePermissions) { if (!serviceAccountWorkspacePermission) {
return next(ServiceAccountNotFoundError({ message: 'Failed to locate Service Account workspace permission' })); return next(ServiceAccountNotFoundError({ message: 'Failed to locate Service Account workspace permission' }));
} }
const serviceAccount = await ServiceAccount.findById(serviceAccountWorkspacePermissions.serviceAccount); const serviceAccount = await ServiceAccount.findById(serviceAccountWorkspacePermission.serviceAccount);
if (!serviceAccount) { if (!serviceAccount) {
return next(ServiceAccountNotFoundError({ message: 'Failed to locate Service Account' })); return next(ServiceAccountNotFoundError({ message: 'Failed to locate Service Account' }));
@@ -49,4 +49,4 @@ const requireServiceAccountWorkspacePermissionsAuth = ({
} }
} }
export default requireServiceAccountWorkspacePermissionsAuth; export default requireServiceAccountWorkspacePermissionAuth;
+6 -6
View File
@@ -12,8 +12,8 @@ import Secret, { ISecret } from './secret';
import ServiceToken, { IServiceToken } from './serviceToken'; import ServiceToken, { IServiceToken } from './serviceToken';
import ServiceAccount, { IServiceAccount } from './serviceAccount'; // new import ServiceAccount, { IServiceAccount } from './serviceAccount'; // new
import ServiceAccountKey, { IServiceAccountKey } from './serviceAccountKey'; // new import ServiceAccountKey, { IServiceAccountKey } from './serviceAccountKey'; // new
import ServiceAccountOrganizationPermissions, { IServiceAccountOrganizationPermissions } from './serviceAccountOrganizationPermission'; // new import ServiceAccountOrganizationPermission, { IServiceAccountOrganizationPermission } from './serviceAccountOrganizationPermission'; // new
import ServiceAccountWorkspacePermissions, { IServiceAccountWorkspacePermissions } from './serviceAccountWorkspacePermissions'; // new import ServiceAccountWorkspacePermission, { IServiceAccountWorkspacePermission } from './serviceAccountWorkspacePermission'; // new
import TokenData, { ITokenData } from './tokenData'; import TokenData, { ITokenData } from './tokenData';
import User, { IUser } from './user'; import User, { IUser } from './user';
import UserAction, { IUserAction } from './userAction'; import UserAction, { IUserAction } from './userAction';
@@ -51,10 +51,10 @@ export {
IServiceAccount, IServiceAccount,
ServiceAccountKey, ServiceAccountKey,
IServiceAccountKey, IServiceAccountKey,
ServiceAccountOrganizationPermissions, ServiceAccountOrganizationPermission,
IServiceAccountOrganizationPermissions, IServiceAccountOrganizationPermission,
ServiceAccountWorkspacePermissions, ServiceAccountWorkspacePermission,
IServiceAccountWorkspacePermissions, IServiceAccountWorkspacePermission,
TokenData, TokenData,
ITokenData, ITokenData,
User, User,
@@ -1,21 +1,16 @@
import { Schema, model, Types, Document } from 'mongoose'; import { Schema, model, Types, Document } from 'mongoose';
export interface IServiceAccountOrganizationPermissions extends Document { export interface IServiceAccountOrganizationPermission extends Document {
_id: Types.ObjectId; _id: Types.ObjectId;
serviceAccount: Types.ObjectId; serviceAccount: Types.ObjectId;
canFoo: boolean;
} }
const serviceAccountOrganizationPermissionsSchema = new Schema<IServiceAccountOrganizationPermissions>( const serviceAccountOrganizationPermissionSchema = new Schema<IServiceAccountOrganizationPermission>(
{ {
serviceAccount: { serviceAccount: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'ServiceAccount', ref: 'ServiceAccount',
required: true required: true
},
canFoo: {
type: Boolean,
default: false
} }
}, },
{ {
@@ -23,6 +18,6 @@ const serviceAccountOrganizationPermissionsSchema = new Schema<IServiceAccountOr
} }
); );
const ServiceAccountOrganizationPermissions = model<IServiceAccountOrganizationPermissions>('ServiceAccountOrganizationPermissions', serviceAccountOrganizationPermissionsSchema); const ServiceAccountOrganizationPermission = model<IServiceAccountOrganizationPermission>('ServiceAccountOrganizationPermission', serviceAccountOrganizationPermissionSchema);
export default ServiceAccountOrganizationPermissions; export default ServiceAccountOrganizationPermission;
@@ -1,6 +1,6 @@
import { Schema, model, Types, Document } from 'mongoose'; import { Schema, model, Types, Document } from 'mongoose';
export interface IServiceAccountWorkspacePermissions extends Document { export interface IServiceAccountWorkspacePermission extends Document {
_id: Types.ObjectId; _id: Types.ObjectId;
serviceAccount: Types.ObjectId; serviceAccount: Types.ObjectId;
workspace: Types.ObjectId; workspace: Types.ObjectId;
@@ -11,7 +11,7 @@ export interface IServiceAccountWorkspacePermissions extends Document {
canDelete: boolean; canDelete: boolean;
} }
const serviceAccountWorkspacePermissions = new Schema<IServiceAccountWorkspacePermissions>( const serviceAccountWorkspacePermissionSchema = new Schema<IServiceAccountWorkspacePermission>(
{ {
serviceAccount: { serviceAccount: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
@@ -49,6 +49,6 @@ const serviceAccountWorkspacePermissions = new Schema<IServiceAccountWorkspacePe
} }
); );
const ServiceAccountWorkspacePermissions = model<IServiceAccountWorkspacePermissions>('ServiceAccountWorkspacePermissions', serviceAccountWorkspacePermissions); const ServiceAccountWorkspacePermission = model<IServiceAccountWorkspacePermission>('ServiceAccountWorkspacePermission', serviceAccountWorkspacePermissionSchema);
export default ServiceAccountWorkspacePermissions; export default ServiceAccountWorkspacePermission;
+34 -32
View File
@@ -5,7 +5,7 @@ import {
requireOrganizationAuth, requireOrganizationAuth,
requireWorkspaceAuth, requireWorkspaceAuth,
requireServiceAccountAuth, requireServiceAccountAuth,
requireServiceAccountWorkspacePermissionsAuth, requireServiceAccountWorkspacePermissionAuth,
validateRequest validateRequest
} from '../../middleware'; } from '../../middleware';
import { param, query, body } from 'express-validator'; import { param, query, body } from 'express-validator';
@@ -76,21 +76,21 @@ router.delete(
serviceAccountsController.deleteServiceAccount serviceAccountsController.deleteServiceAccount
); );
router.get( // router.get(
'/:serviceAccountId/permissions/organization', // '/:serviceAccountId/permissions/organization',
param('serviceAccountId').exists().isString().trim(), // param('serviceAccountId').exists().isString().trim(),
query('offset').exists(), // query('offset').exists(),
query('limit').exists(), // query('limit').exists(),
validateRequest, // validateRequest,
requireAuth({ // requireAuth({
acceptedAuthModes: ['jwt'] // acceptedAuthModes: ['jwt']
}), // }),
requireServiceAccountAuth({ // requireServiceAccountAuth({
acceptedRoles: [OWNER, ADMIN], // acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED] // acceptedStatuses: [ACCEPTED]
}), // }),
serviceAccountsController.getServiceAccountOrganizationPermissions // serviceAccountsController.getServiceAccountOrganizationPermission
); // );
router.get( router.get(
'/:serviceAccountId/permissions/workspace', '/:serviceAccountId/permissions/workspace',
@@ -106,19 +106,19 @@ router.get(
serviceAccountsController.getServiceAccountWorkspacePermissions serviceAccountsController.getServiceAccountWorkspacePermissions
); );
router.post( // router.post(
'/:serviceAccountId/permissions/organization', // '/:serviceAccountId/permissions/organization',
param('serviceAccountId').exists().isString().trim(), // param('serviceAccountId').exists().isString().trim(),
validateRequest, // validateRequest,
requireAuth({ // requireAuth({
acceptedAuthModes: ['jwt'] // acceptedAuthModes: ['jwt']
}), // }),
requireServiceAccountAuth({ // requireServiceAccountAuth({
acceptedRoles: [OWNER, ADMIN], // acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED] // acceptedStatuses: [ACCEPTED]
}), // }),
serviceAccountsController.addServiceAccountOrganizationPermission // serviceAccountsController.addServiceAccountOrganizationPermission
); // );
router.post( router.post(
'/:serviceAccountId/permissions/workspace', '/:serviceAccountId/permissions/workspace',
@@ -129,6 +129,8 @@ router.post(
body('canWrite').isBoolean().optional(), body('canWrite').isBoolean().optional(),
body('canUpdate').isBoolean().optional(), body('canUpdate').isBoolean().optional(),
body('canDelete').isBoolean().optional(), body('canDelete').isBoolean().optional(),
body('encryptedKey').exists().isString().notEmpty(),
body('nonce').exists().isString().notEmpty(),
validateRequest, validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt'] acceptedAuthModes: ['jwt']
@@ -145,9 +147,9 @@ router.post(
); );
router.delete( router.delete(
'/:serviceAccountId/permissions/workspace/:serviceAccountWorkspacePermissionsId', '/:serviceAccountId/permissions/workspace/:serviceAccountWorkspacePermissionId',
param('serviceAccountId').exists().isString().trim(), param('serviceAccountId').exists().isString().trim(),
param('serviceAccountWorkspacePermissionsId').exists().isString().trim(), param('serviceAccountWorkspacePermissionId').exists().isString().trim(),
validateRequest, validateRequest,
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt'] acceptedAuthModes: ['jwt']
@@ -156,7 +158,7 @@ router.delete(
acceptedRoles: [OWNER, ADMIN], acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED] acceptedStatuses: [ACCEPTED]
}), }),
requireServiceAccountWorkspacePermissionsAuth({ requireServiceAccountWorkspacePermissionAuth({
acceptedRoles: [OWNER, ADMIN], acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED] acceptedStatuses: [ACCEPTED]
}), }),
@@ -26,6 +26,31 @@ type EncryptAsymmetricProps = {
privateKey: string; privateKey: string;
}; };
/**
* Verify that private key [privateKey] is the one that corresponds to
* the public key [publicKey]
* @param {Object}
* @param {String} - base64-encoded Nacl private key
* @param {String} - base64-encoded Nacl public key
*/
const verifyPrivateKey = ({
privateKey,
publicKey
}: {
privateKey: string;
publicKey: string;
}) => {
const derivedPublicKey = nacl.util.encodeBase64(
nacl.box.keyPair.fromSecretKey(
nacl.util.decodeBase64(privateKey)
).publicKey
);
if (derivedPublicKey !== publicKey) {
throw new Error('Failed to verify private key');
}
}
/** /**
* Derive a key from password [password] and salt [salt] using Argon2id * Derive a key from password [password] and salt [salt] using Argon2id
* @param {Object} obj * @param {Object} obj
@@ -204,5 +229,6 @@ export {
decryptSymmetric, decryptSymmetric,
deriveArgonKey, deriveArgonKey,
encryptAssymmetric, encryptAssymmetric,
encryptSymmetric, encryptSymmetric,
generateKeyPair}; generateKeyPair,
verifyPrivateKey};
@@ -1,9 +1,10 @@
export { export {
useCreateServiceAccount, useCreateServiceAccount,
useCreateServiceAccountProjectLevelPermissions, useCreateServiceAccountProjectLevelPermission,
useDeleteServiceAccount, useDeleteServiceAccount,
useDeleteServiceAccountProjectLevelPermissions, useDeleteServiceAccountProjectLevelPermission,
useGetServiceAccountById, useGetServiceAccountById,
useGetServiceAccountProjectLevelPermissions, useGetServiceAccountProjectLevelPermissions,
useGetServiceAccounts, useGetServiceAccounts,
useRenameServiceAccount} from './queries'; useRenameServiceAccount
} from './queries';
@@ -5,14 +5,12 @@ import { apiRequest } from '@app/config/request';
import { import {
CreateServiceAccountDTO, CreateServiceAccountDTO,
CreateServiceAccountRes, CreateServiceAccountRes,
CreateServiceAccountWorkspacePermissionsDTO, CreateServiceAccountWorkspacePermissionDTO,
DeleteServiceAccountRes, DeleteServiceAccountWorkspacePermissionDTO,
DeleteServiceAccountWorkspacePermissionsDTO,
DeleteServiceAccountWorkspacePermissionsRes,
RenameServiceAccountDTO, RenameServiceAccountDTO,
RenameServiceAccountRes,
ServiceAccount, ServiceAccount,
ServiceAccountWorkspacePermissions} from './types'; ServiceAccountWorkspacePermission
} from './types';
const serviceAccountKeys = { const serviceAccountKeys = {
getServiceAccountById: (serviceAccountId: string) => [{ serviceAccountId }, 'service-account'] as const, getServiceAccountById: (serviceAccountId: string) => [{ serviceAccountId }, 'service-account'] as const,
@@ -68,7 +66,7 @@ export const useCreateServiceAccount = () => {
export const useRenameServiceAccount = () => { export const useRenameServiceAccount = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<RenameServiceAccountRes, {}, RenameServiceAccountDTO>({ return useMutation<ServiceAccount, {}, RenameServiceAccountDTO>({
mutationFn: async ({ serviceAccountId, name }) => { mutationFn: async ({ serviceAccountId, name }) => {
const { data: { serviceAccount } } = await apiRequest.patch(`/api/v2/service-accounts/${serviceAccountId}/name`, { name }); const { data: { serviceAccount } } = await apiRequest.patch(`/api/v2/service-accounts/${serviceAccountId}/name`, { name });
return serviceAccount; return serviceAccount;
@@ -83,7 +81,7 @@ export const useRenameServiceAccount = () => {
export const useDeleteServiceAccount = () => { export const useDeleteServiceAccount = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<DeleteServiceAccountRes, {}, string>({ return useMutation<ServiceAccount, {}, string>({
mutationFn: async (serviceAccountId) => { mutationFn: async (serviceAccountId) => {
const { data: { serviceAccount } } = await apiRequest.delete(`/api/v2/service-accounts/${serviceAccountId}`); const { data: { serviceAccount } } = await apiRequest.delete(`/api/v2/service-accounts/${serviceAccountId}`);
return serviceAccount; return serviceAccount;
@@ -95,14 +93,11 @@ export const useDeleteServiceAccount = () => {
} }
const fetchServiceAccountProjectLevelPermissions = async (serviceAccountId: string) => { const fetchServiceAccountProjectLevelPermissions = async (serviceAccountId: string) => {
const { data: { permissions } } = await apiRequest.get<{ permissions: ServiceAccountWorkspacePermissions[] }>( const { data: { serviceAccountWorkspacePermissions } } = await apiRequest.get<{ serviceAccountWorkspacePermissions: ServiceAccountWorkspacePermission[] }>(
`/api/v2/service-accounts/${serviceAccountId}/permissions/workspace` `/api/v2/service-accounts/${serviceAccountId}/permissions/workspace`
); );
console.log('fetchServiceAccountProjectLevelPermissions'); return serviceAccountWorkspacePermissions;
console.log('prrr: ', permissions);
return permissions;
} }
export const useGetServiceAccountProjectLevelPermissions = (serviceAccountId: string) => { export const useGetServiceAccountProjectLevelPermissions = (serviceAccountId: string) => {
@@ -113,13 +108,13 @@ export const useGetServiceAccountProjectLevelPermissions = (serviceAccountId: st
}); });
} }
export const useCreateServiceAccountProjectLevelPermissions = () => { export const useCreateServiceAccountProjectLevelPermission = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<CreateServiceAccountRes, {}, CreateServiceAccountWorkspacePermissionsDTO>({ return useMutation<ServiceAccountWorkspacePermission, {}, CreateServiceAccountWorkspacePermissionDTO>({
mutationFn: async (body) => { mutationFn: async (body) => {
const { data: { permissions } } = await apiRequest.post(`/api/v2/service-accounts/${body.serviceAccountId}/permissions/workspace`, body); const { data: { serviceAccountWorkspacePermission } } = await apiRequest.post(`/api/v2/service-accounts/${body.serviceAccountId}/permissions/workspace`, body);
return permissions; return serviceAccountWorkspacePermission;
}, },
onSuccess: ({ serviceAccount }) => { onSuccess: ({ serviceAccount }) => {
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccountProjectLevelPermissions(serviceAccount)); queryClient.invalidateQueries(serviceAccountKeys.getServiceAccountProjectLevelPermissions(serviceAccount));
@@ -127,20 +122,16 @@ export const useCreateServiceAccountProjectLevelPermissions = () => {
}); });
} }
export const useDeleteServiceAccountProjectLevelPermissions = () => { export const useDeleteServiceAccountProjectLevelPermission = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<DeleteServiceAccountWorkspacePermissionsRes, {}, DeleteServiceAccountWorkspacePermissionsDTO>({ return useMutation<ServiceAccountWorkspacePermission, {}, DeleteServiceAccountWorkspacePermissionDTO>({
mutationFn: async ({ serviceAccountId, serviceAccountWorkspacePermissionsId }) => { mutationFn: async ({ serviceAccountId, serviceAccountWorkspacePermissionId }) => {
const { data: { permissions } } = await apiRequest.delete(`/api/v2/service-accounts/${serviceAccountId}/permissions/workspace/${serviceAccountWorkspacePermissionsId}`); const { data: { serviceAccountWorkspacePermission} } = await apiRequest.delete(`/api/v2/service-accounts/${serviceAccountId}/permissions/workspace/${serviceAccountWorkspacePermissionId}`);
console.log('useDeleteServiceAccountProjectLevelPermissions'); return serviceAccountWorkspacePermission;
console.log('permissions: ', permissions);
return permissions;
}, },
onSuccess: ({ serviceAccount }) => { onSuccess: (serviceAccountWorkspacePermission) => {
console.log('onSuccess3: ', serviceAccount); queryClient.invalidateQueries(serviceAccountKeys.getServiceAccountProjectLevelPermissions(serviceAccountWorkspacePermission.serviceAccount));
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccountProjectLevelPermissions(serviceAccount));
// queryClient.invalidateQueries(serviceAccountKeys.getServiceAccounts(organization));
} }
}); });
} }
@@ -19,20 +19,13 @@ export type CreateServiceAccountRes = {
serviceAccountAccessKey: string; serviceAccountAccessKey: string;
} }
export type DeleteServiceAccountRes = {
serviceAccount: ServiceAccount;
}
export type RenameServiceAccountDTO = { export type RenameServiceAccountDTO = {
serviceAccountId: string; serviceAccountId: string;
name: string; name: string;
} }
export type RenameServiceAccountRes = { export type ServiceAccountWorkspacePermission = {
serviceAccount: ServiceAccount; _id: string;
}
export type ServiceAccountWorkspacePermissions = {
serviceAccount: string; serviceAccount: string;
workspace: string; workspace: string;
environment: string; environment: string;
@@ -42,7 +35,7 @@ export type ServiceAccountWorkspacePermissions = {
canDelete: boolean; canDelete: boolean;
} }
export type CreateServiceAccountWorkspacePermissionsDTO = { export type CreateServiceAccountWorkspacePermissionDTO = {
serviceAccountId: string; serviceAccountId: string;
workspaceId: string; workspaceId: string;
environment: string; environment: string;
@@ -50,17 +43,11 @@ export type CreateServiceAccountWorkspacePermissionsDTO = {
canWrite: boolean; canWrite: boolean;
canUpdate: boolean; canUpdate: boolean;
canDelete: boolean; canDelete: boolean;
encryptedKey: string;
nonce: string;
} }
export type CreateServiceAccountWorkspacePermissionsRes = { export type DeleteServiceAccountWorkspacePermissionDTO = {
permissions: ServiceAccountWorkspacePermissions
}
export type DeleteServiceAccountWorkspacePermissionsDTO = {
serviceAccountId: string; serviceAccountId: string;
serviceAccountWorkspacePermissionsId: string; serviceAccountWorkspacePermissionId: string;
}
export type DeleteServiceAccountWorkspacePermissionsRes = {
permissions: ServiceAccountWorkspacePermissions
} }
+7 -2
View File
@@ -136,8 +136,13 @@ export const AppLayout = ({ children }: LayoutProps) => {
} else if (pathSegments.length >= 3 && pathSegments[0] === 'settings') { } else if (pathSegments.length >= 3 && pathSegments[0] === 'settings') {
[, , intendedWorkspaceId] = pathSegments; [, , intendedWorkspaceId] = pathSegments;
} else { } else {
const lastPathSegment = router.asPath.split('/').pop().split('?'); const lastPathSegments = router.asPath.split('/').pop();
[intendedWorkspaceId] = lastPathSegment; if (lastPathSegments !== undefined) {
[intendedWorkspaceId] = lastPathSegments.split('?');
}
// const lastPathSegment = router.asPath.split('/').pop().split('?');
// [intendedWorkspaceId] = lastPathSegment;
} }
if (!intendedWorkspaceId) return; if (!intendedWorkspaceId) return;
@@ -1,22 +1,13 @@
import SecurityClient from '@app/components/utilities/SecurityClient'; import { apiRequest } from '@app/config/request';
/** /**
* Get the latest key pairs from a certain workspace * Get the latest key pairs from a certain workspace
* @param {string} workspaceId * @param {string} workspaceId
* @returns * @returns
*/ */
const getLatestFileKey = ({ workspaceId }: { workspaceId: string }) => const getLatestFileKey = async ({ workspaceId }: { workspaceId: string }) => {
SecurityClient.fetchCall(`/api/v1/key/${workspaceId}/latest`, { const { data } = await apiRequest.get(`/api/v1/key/${workspaceId}/latest`);
method: 'GET', return data;
headers: { }
'Content-Type': 'application/json'
}
}).then(async (res) => {
if (res?.status === 200) {
return res.json();
}
console.log('Failed to get the latest key pairs for a certain project');
return undefined;
});
export default getLatestFileKey; export default getLatestFileKey;
@@ -1,23 +1,17 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { getTranslatedServerSideProps } from '@app/components/utilities/withTranslateProps'; import { getTranslatedServerSideProps } from '@app/components/utilities/withTranslateProps';
import { CreateServiceAccountPage } from '@app/views/Settings/CreateServiceAccountPage'; import { CreateServiceAccountPage } from '@app/views/Settings/CreateServiceAccountPage';
export default function ServiceAccountPage() { export default function ServiceAccountPage() {
const router = useRouter();
// const { orgId, serviceAccountId } = router.query;
const { t } = useTranslation();
return ( return (
<> <>
<Head> <Head>
<title>Edit Service Account</title> <title>Edit Service Account</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
<div />
<CreateServiceAccountPage /> <CreateServiceAccountPage />
</> </>
); );
@@ -25,7 +19,7 @@ export default function ServiceAccountPage() {
ServiceAccountPage.requireAuth = true; ServiceAccountPage.requireAuth = true;
export const getServerSidePros = getTranslatedServerSideProps([ export const getServerSideProps = getTranslatedServerSideProps([
'settings', 'settings',
'settings-org', 'settings-org',
'section-incident', 'section-incident',
@@ -3,11 +3,13 @@ import { useRouter } from 'next/router';
import NavHeader from '@app/components/navigation/NavHeader'; import NavHeader from '@app/components/navigation/NavHeader';
import { SAProjectLevelPermissionsTable } from './components/SAProjectLevelPermissionsTable'; import { SAProjectLevelPermissionsTable } from './components/SAProjectLevelPermissionsTable';
import { ServiceAccountNameChangeSection } from './components'; import {
CopyServiceAccountIDSection,
ServiceAccountNameChangeSection} from './components';
export const CreateServiceAccountPage = () => { export const CreateServiceAccountPage = () => {
const router = useRouter(); const router = useRouter();
const { serviceAccountId }: { serviceAccountId: string } = router.query; const {serviceAccountId} = router.query;
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
@@ -21,23 +23,23 @@ export const CreateServiceAccountPage = () => {
A service account represents a machine identity such as a VM or application client. A service account represents a machine identity such as a VM or application client.
</p> </p>
</div> </div>
<div className="max-w-8xl mx-6"> {typeof serviceAccountId === 'string' && (
{typeof serviceAccountId === 'string' && ( <div className="max-w-8xl mx-6">
<ServiceAccountNameChangeSection <ServiceAccountNameChangeSection
serviceAccountId={serviceAccountId} serviceAccountId={serviceAccountId}
/> />
)} <div className="mt-8">
{/* <div className="rounded-md bg-white/5 p-6 mt-6"> <CopyServiceAccountIDSection
<p className="mb-4 text-xl font-semibold">Organization-Level Permissions</p> serviceAccountId={serviceAccountId}
<SAProjectLevelPermissionsTable /> />
</div> */} </div>
<div className="rounded-md bg-white/5 p-6 mt-6"> <div className="mt-8">
<p className="mb-4 text-xl font-semibold">Project-Level Permissions</p> <SAProjectLevelPermissionsTable
<SAProjectLevelPermissionsTable serviceAccountId={serviceAccountId}
serviceAccountId={serviceAccountId} />
/> </div>
</div> </div>
</div> )}
</div> </div>
); );
} }
@@ -0,0 +1,49 @@
import { useEffect } from 'react';
import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconButton } from '@app/components/v2';
import { useToggle } from '@app/hooks';
type Props = {
serviceAccountId: string;
}
export const CopyServiceAccountIDSection = ({ serviceAccountId }: Props): JSX.Element => {
const [isServiceAccountIdCopied, setIsServiceAccountIdCopied] = useToggle(false);
useEffect(() => {
let timer: NodeJS.Timeout;
if (isServiceAccountIdCopied) {
timer = setTimeout(() => setIsServiceAccountIdCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [isServiceAccountIdCopied]);
const copyServiceAccountIdToClipboard = () => {
navigator.clipboard.writeText(serviceAccountId);
setIsServiceAccountIdCopied.on();
};
return (
<div className="flex w-full flex-col items-start rounded-md bg-white/5 px-6 p-6">
<p className="text-xl font-semibold">Service Account ID</p>
<div className="mt-4 flex items-center justify-end rounded-md bg-white/[0.07] text-base text-gray-400 p-2">
<p className="mr-4 break-all">{serviceAccountId}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => copyServiceAccountIdToClipboard()}
>
<FontAwesomeIcon icon={isServiceAccountIdCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
</div>
</div>
);
}
@@ -0,0 +1 @@
export { CopyServiceAccountIDSection } from './CopyServiceAccountIDSection';
@@ -9,6 +9,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup'; import * as yup from 'yup';
import {
decryptAssymmetric,
encryptAssymmetric,
verifyPrivateKey} from '@app/components/utilities/cryptography/crypto';
import { import {
Button, Button,
Checkbox, Checkbox,
@@ -32,12 +36,15 @@ import {
Tr} from '@app/components/v2'; Tr} from '@app/components/v2';
import { usePopUp } from '@app/hooks'; import { usePopUp } from '@app/hooks';
import { import {
useCreateServiceAccountProjectLevelPermissions, useCreateServiceAccountProjectLevelPermission,
useDeleteServiceAccountProjectLevelPermissions, useDeleteServiceAccountProjectLevelPermission,
useGetServiceAccountById,
useGetServiceAccountProjectLevelPermissions, useGetServiceAccountProjectLevelPermissions,
useGetUserWorkspaces} from '@app/hooks/api'; useGetUserWorkspaces} from '@app/hooks/api';
import getLatestFileKey from '@app/pages/api/workspace/getLatestFileKey';
const createProjectLevelPermissionSchema = yup.object({ const createProjectLevelPermissionSchema = yup.object({
privateKey: yup.string().required().label('Private Key'),
workspace: yup.string().required().label('Workspace'), workspace: yup.string().required().label('Workspace'),
environment: yup.string().required().label('Environment'), environment: yup.string().required().label('Environment'),
permissions: yup.object().shape({ permissions: yup.object().shape({
@@ -57,18 +64,19 @@ type Props = {
export const SAProjectLevelPermissionsTable = ({ export const SAProjectLevelPermissionsTable = ({
serviceAccountId serviceAccountId
}: Props) => { }: Props) => {
const { data: serviceAccount } = useGetServiceAccountById(serviceAccountId);
const { data: userWorkspaces, isLoading: isUserWorkspacesLoading } = useGetUserWorkspaces(); const { data: userWorkspaces, isLoading: isUserWorkspacesLoading } = useGetUserWorkspaces();
const [searchPermissions, setSearchPermissions] = useState(''); const [searchPermissions, setSearchPermissions] = useState('');
const [defaultValues, setDefaultValues] = useState<CreateProjectLevelPermissionForm | undefined>(undefined); const [defaultValues, setDefaultValues] = useState<CreateProjectLevelPermissionForm | undefined>(undefined);
const { data: permissions, isLoading: isPermissionsLoading } = useGetServiceAccountProjectLevelPermissions(serviceAccountId); const { data: serviceAccountWorkspacePermissions, isLoading: isPermissionsLoading } = useGetServiceAccountProjectLevelPermissions(serviceAccountId);
const createServiceAccountProjectLevelPermissions = useCreateServiceAccountProjectLevelPermissions(); const createServiceAccountProjectLevelPermission = useCreateServiceAccountProjectLevelPermission();
const deleteServiceAccountProjectLevelPermissions = useDeleteServiceAccountProjectLevelPermissions(); const deleteServiceAccountProjectLevelPermission = useDeleteServiceAccountProjectLevelPermission();
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
'addProjectLevelPermissions', 'addProjectLevelPermission',
'removeProjectLevelPermissions', 'removeProjectLevelPermission',
] as const); ] as const);
const { const {
@@ -78,35 +86,68 @@ export const SAProjectLevelPermissionsTable = ({
formState: { isSubmitting } formState: { isSubmitting }
} = useForm<CreateProjectLevelPermissionForm>({ resolver: yupResolver(createProjectLevelPermissionSchema), defaultValues }) } = useForm<CreateProjectLevelPermissionForm>({ resolver: yupResolver(createProjectLevelPermissionSchema), defaultValues })
const onAddProjectLevelPermissions = async ({ const onAddProjectLevelPermission = async ({
privateKey,
workspace, workspace,
environment, environment,
permissions: { canRead, canWrite, canUpdate, canDelete } permissions: { canRead, canWrite, canUpdate, canDelete }
}: CreateProjectLevelPermissionForm) => { }: CreateProjectLevelPermissionForm) => {
await createServiceAccountProjectLevelPermissions.mutateAsync({
// TODO: clean up / modularize this function
if (!serviceAccount) return;
const { latestKey } = await getLatestFileKey({
workspaceId: workspace
});
verifyPrivateKey({
privateKey,
publicKey: serviceAccount.publicKey
});
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
const key = decryptAssymmetric({
ciphertext: latestKey.encryptedKey,
nonce: latestKey.nonce,
publicKey: latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: serviceAccount.publicKey,
privateKey: PRIVATE_KEY
});
await createServiceAccountProjectLevelPermission.mutateAsync({
serviceAccountId, serviceAccountId,
workspaceId: workspace, workspaceId: workspace,
environment, environment,
canRead, canRead,
canWrite, canWrite,
canUpdate, canUpdate,
canDelete canDelete,
encryptedKey: ciphertext,
nonce
}); });
handlePopUpClose('addProjectLevelPermissions'); handlePopUpClose('addProjectLevelPermission');
} }
const onRemoveProjectLevelPermissions = async () => { const onRemoveProjectLevelPermission = async () => {
const serviceAccountWorkspacePermissionsId = (popUp?.removeProjectLevelPermissions?.data as { _id: string })?._id; const serviceAccountWorkspacePermissionId = (popUp?.removeProjectLevelPermission?.data as { _id: string })?._id;
await deleteServiceAccountProjectLevelPermissions.mutateAsync({ await deleteServiceAccountProjectLevelPermission.mutateAsync({
serviceAccountId, serviceAccountId,
serviceAccountWorkspacePermissionsId serviceAccountWorkspacePermissionId
}); });
handlePopUpClose('removeProjectLevelPermissions'); handlePopUpClose('removeProjectLevelPermission');
} }
useEffect(() => { useEffect(() => {
if (userWorkspaces) { if (userWorkspaces) {
setDefaultValues({ setDefaultValues({
privateKey: '',
workspace: String(userWorkspaces?.[0]?._id), workspace: String(userWorkspaces?.[0]?._id),
environment: String(userWorkspaces?.[0]?.environments?.[0]?.slug), environment: String(userWorkspaces?.[0]?.environments?.[0]?.slug),
permissions: { permissions: {
@@ -118,10 +159,10 @@ export const SAProjectLevelPermissionsTable = ({
}); });
} }
}, [userWorkspaces]); }, [userWorkspaces]);
return ( return (
<div className="w-full"> <div className="w-full bg-white/5 p-6">
<p className="mb-4 text-xl font-semibold">Project-Level Permissions</p>
<div className="mb-4 flex"> <div className="mb-4 flex">
<div className="mr-4 flex-1"> <div className="mr-4 flex-1">
<Input <Input
@@ -134,7 +175,7 @@ export const SAProjectLevelPermissionsTable = ({
<Button <Button
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => { onClick={() => {
handlePopUpOpen('addProjectLevelPermissions') handlePopUpOpen('addProjectLevelPermission')
reset(); reset();
}} }}
> >
@@ -156,8 +197,8 @@ export const SAProjectLevelPermissionsTable = ({
</THead> </THead>
<TBody> <TBody>
{isPermissionsLoading && <TableSkeleton columns={6} key="service-account-project-level-permissions" />} {isPermissionsLoading && <TableSkeleton columns={6} key="service-account-project-level-permissions" />}
{!isPermissionsLoading && permissions && ( {!isPermissionsLoading && serviceAccountWorkspacePermissions && (
permissions.map(({ serviceAccountWorkspacePermissions.map(({
_id, _id,
workspace, workspace,
environment, environment,
@@ -196,13 +237,14 @@ export const SAProjectLevelPermissionsTable = ({
<Checkbox <Checkbox
id="isDeletePermissionEnabled" id="isDeletePermissionEnabled"
isChecked={canDelete} isChecked={canDelete}
isDisabled
/> />
</Td> </Td>
<Td> <Td>
<IconButton <IconButton
ariaLabel="delete" ariaLabel="delete"
colorSchema="danger" colorSchema="danger"
onClick={() => handlePopUpOpen('removeProjectLevelPermissions', { _id })} onClick={() => handlePopUpOpen('removeProjectLevelPermission', { _id })}
> >
<FontAwesomeIcon icon={faTrash} /> <FontAwesomeIcon icon={faTrash} />
</IconButton> </IconButton>
@@ -211,7 +253,7 @@ export const SAProjectLevelPermissionsTable = ({
); );
}) })
)} )}
{!isPermissionsLoading && permissions?.length === 0 && ( {!isPermissionsLoading && serviceAccountWorkspacePermissions?.length === 0 && (
<Tr> <Tr>
<Td colSpan={7} className="py-6 text-center text-bunker-400"> <Td colSpan={7} className="py-6 text-center text-bunker-400">
<EmptyState title="No permissions found" icon={faKey} /> <EmptyState title="No permissions found" icon={faKey} />
@@ -222,18 +264,32 @@ export const SAProjectLevelPermissionsTable = ({
</Table> </Table>
</TableContainer> </TableContainer>
<Modal <Modal
isOpen={popUp?.addProjectLevelPermissions?.isOpen} isOpen={popUp?.addProjectLevelPermission?.isOpen}
onOpenChange={(isOpen) => { onOpenChange={(isOpen) => {
handlePopUpToggle('addProjectLevelPermissions', isOpen); handlePopUpToggle('addProjectLevelPermission', isOpen);
}} }}
> >
<ModalContent <ModalContent
title="Add a Project-Level Permission" title="Add a Project-Level Permission"
subTitle="The service account will be granted scoped access to the specified project and environment" subTitle="The service account will be granted scoped access to the specified project and environment"
> >
<form onSubmit={handleSubmit(onAddProjectLevelPermissions)}> <form onSubmit={handleSubmit(onAddProjectLevelPermission)}>
{!isUserWorkspacesLoading && userWorkspaces && ( {!isUserWorkspacesLoading && userWorkspaces && (
<> <>
<Controller
control={control}
defaultValue=""
name="privateKey"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Service Account Private Key"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="workspace" name="workspace"
@@ -383,11 +439,11 @@ export const SAProjectLevelPermissionsTable = ({
</ModalContent> </ModalContent>
</Modal> </Modal>
<DeleteActionModal <DeleteActionModal
isOpen={popUp.removeProjectLevelPermissions.isOpen} isOpen={popUp.removeProjectLevelPermission.isOpen}
deleteKey="remove" deleteKey="remove"
title="Do you want to remove this permission from the service account?" title="Do you want to remove this permission from the service account?"
onChange={(isOpen) => handlePopUpToggle('removeProjectLevelPermissions', isOpen)} onChange={(isOpen) => handlePopUpToggle('removeProjectLevelPermission', isOpen)}
onDeleteApproved={onRemoveProjectLevelPermissions} onDeleteApproved={onRemoveProjectLevelPermission}
/> />
</div> </div>
); );
@@ -1,2 +1,3 @@
export { CopyServiceAccountIDSection } from './CopyServiceAccountIDSection';
export { SAProjectLevelPermissionsTable } from './SAProjectLevelPermissionsTable'; export { SAProjectLevelPermissionsTable } from './SAProjectLevelPermissionsTable';
export { ServiceAccountNameChangeSection } from './ServiceAccountNameChangeSection'; export { ServiceAccountNameChangeSection } from './ServiceAccountNameChangeSection';
@@ -62,8 +62,6 @@ export const OrgServiceAccountsTable = () => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
console.log('currentWorkspace: ', currentWorkspace);
const orgId = currentOrg?._id || ''; const orgId = currentOrg?._id || '';
const [step, setStep] = useState(0); const [step, setStep] = useState(0);
const [isAccessKeyCopied, setIsAccessKeyCopied] = useToggle(false); const [isAccessKeyCopied, setIsAccessKeyCopied] = useToggle(false);
@@ -114,8 +112,6 @@ export const OrgServiceAccountsTable = () => {
expiresIn expiresIn
}); });
console.log('serviceAccountDetails: ', serviceAccountDetails);
setAccessKey(serviceAccountDetails.serviceAccountAccessKey); setAccessKey(serviceAccountDetails.serviceAccountAccessKey);
setStep(1); setStep(1);
@@ -123,11 +119,7 @@ export const OrgServiceAccountsTable = () => {
} }
const onRemoveServiceAccount = async () => { const onRemoveServiceAccount = async () => {
console.log('onRemoveServiceAccount');
const serviceAccountId = (popUp?.removeServiceAccount?.data as { _id: string })?._id; const serviceAccountId = (popUp?.removeServiceAccount?.data as { _id: string })?._id;
console.log('serviceAccountId: ', serviceAccountId);
await removeServiceAccount.mutateAsync(serviceAccountId); await removeServiceAccount.mutateAsync(serviceAccountId);
handlePopUpClose('removeServiceAccount'); handlePopUpClose('removeServiceAccount');
} }
@@ -247,8 +239,6 @@ export const OrgServiceAccountsTable = () => {
} }
} }
console.log('serviceAccounts: ', serviceAccounts);
return ( return (
<div className="w-full"> <div className="w-full">
<div className="mb-4 flex"> <div className="mb-4 flex">