Remove/deprecate service accounts + old logs/actions

pull/1157/head
Tuan Dang 7 months ago
parent c29a11866e
commit 50ce977c55

@ -6,14 +6,8 @@ const jsrp = require("jsrp");
import { LoginSRPDetail, TokenVersion, User } from "../../models";
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user";
import {
ACTION_LOGIN,
ACTION_LOGOUT,
AuthTokenType
} from "../../variables";
import { AuthTokenType } from "../../variables";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { EELogService } from "../../ee/services";
import { getUserAgentType } from "../../utils/posthog";
import {
getAuthSecret,
getHttpsEnabled,
@ -145,19 +139,6 @@ export const login2 = async (req: Request, res: Response) => {
secure: await getHttpsEnabled()
});
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
});
loginAction &&
(await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
// return (access) token in response
return res.status(200).send({
token: tokens.token,
@ -194,19 +175,6 @@ export const logout = async (req: Request, res: Response) => {
secure: (await getHttpsEnabled()) as boolean
});
const logoutAction = await EELogService.createAction({
name: ACTION_LOGOUT,
userId: req.user._id
});
logoutAction &&
(await EELogService.createLog({
userId: req.user._id,
actions: [logoutAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
return res.status(200).send({
message: "Successfully logged out."
});

@ -8,10 +8,8 @@ import { createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user";
import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services";
import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors";
import { ACTION_LOGIN, AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
import { AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth";
@ -190,19 +188,6 @@ export const login2 = async (req: Request, res: Response) => {
response.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
});
loginAction &&
(await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.ip
}));
return res.status(200).send(response);
}
@ -330,18 +315,5 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
resObj.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
});
loginAction &&
(await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
return res.status(200).send(resObj);
};

@ -23,7 +23,6 @@ import {
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { SecretImport } from "../../models";
import { ServiceAccountWorkspacePermission } from "../../models";
import { Webhook } from "../../models";
/**
@ -400,11 +399,6 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
{ arrayFilters: [{ "element.environment": oldEnvironmentSlug }] },
);
await ServiceAccountWorkspacePermission.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await Webhook.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }

@ -6,7 +6,6 @@ import * as workspaceController from "./workspaceController";
import * as serviceTokenDataController from "./serviceTokenDataController";
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";
import * as membershipController from "./membershipController";
@ -20,7 +19,6 @@ export {
serviceTokenDataController,
secretController,
secretsController,
serviceAccountsController,
environmentController,
tagController,
membershipController

@ -3,7 +3,6 @@ import { Types } from "mongoose";
import {
Membership,
MembershipOrg,
ServiceAccount,
Workspace
} from "../../models";
import { Role } from "../../ee/models";
@ -330,23 +329,6 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
});
};
/**
* 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({
serviceAccounts
});
};
/**
* Create new organization named [organizationName]
* and add user as owner

@ -1,12 +1,8 @@
import { Types } from "mongoose";
import { Request, Response } from "express";
import { Folder, ISecret, Secret, ServiceTokenData, Tag } from "../../models";
import { AuditLog, EventType, IAction, SecretVersion } from "../../ee/models";
import { AuditLog, EventType, SecretVersion } from "../../ee/models";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
K8_USER_AGENT_NAME,
@ -15,7 +11,7 @@ import {
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { EventService } from "../../services";
import { eventPushSecrets } from "../../events";
import { EEAuditLogService, EELogService, EESecretService } from "../../ee/services";
import { EEAuditLogService, EESecretService } from "../../ee/services";
import { SecretService, TelemetryService } from "../../services";
import { getUserAgentType } from "../../utils/posthog";
import { PERMISSION_WRITE_SECRETS } from "../../variables";
@ -82,7 +78,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
const createSecrets: any[] = [];
const updateSecrets: any[] = [];
const deleteSecrets: { _id: Types.ObjectId; secretName: string }[] = [];
const actions: IAction[] = [];
// get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({
@ -224,16 +219,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
await AuditLog.insertMany(auditLogs);
const addAction = (await EELogService.createAction({
name: ACTION_ADD_SECRETS,
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: createdSecrets.map((n) => n._id)
})) as IAction;
actions.push(addAction);
if (postHogClient) {
postHogClient.capture({
event: "secrets added",
@ -350,14 +335,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
await AuditLog.insertMany(auditLogs);
const updateAction = (await EELogService.createAction({
name: ACTION_UPDATE_SECRETS,
userId: req.user._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: updatedSecrets.map((u) => u._id)
})) as IAction;
actions.push(updateAction);
if (postHogClient) {
postHogClient.capture({
event: "secrets modified",
@ -428,14 +405,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
await AuditLog.insertMany(auditLogs);
const deleteAction = (await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
userId: req.user._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: deleteSecretIds
})) as IAction;
actions.push(deleteAction);
if (postHogClient) {
postHogClient.capture({
event: "secrets deleted",
@ -451,17 +420,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
}
}
if (actions.length > 0) {
// (EE) create (audit) log
await EELogService.createLog({
userId: req.user._id.toString(),
workspaceId: new Types.ObjectId(workspaceId),
actions,
channel,
ipAddress: req.realIP
});
}
// // trigger event - push secrets
await EventService.handleEvent({
event: eventPushSecrets({
@ -731,27 +689,6 @@ export const createSecrets = async (req: Request, res: Response) => {
)
});
const addAction = await EELogService.createAction({
name: ACTION_ADD_SECRETS,
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: newlyCreatedSecrets.map((n) => n._id)
});
// (EE) create (audit) log
addAction &&
(await EELogService.createLog({
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId),
actions: [addAction],
channel,
ipAddress: req.realIP
}));
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId: new Types.ObjectId(workspaceId),
@ -960,22 +897,6 @@ export const getSecrets = async (req: Request, res: Response) => {
secrets = await Secret.find(secretQuery).populate("tags");
}
// case: client authorization is via service account
if (req.serviceAccount) {
const secretQuery: any = {
workspace: workspaceId,
environment,
folder: folderId,
user: { $exists: false } // shared secrets only from workspace
};
if (tagIds.length > 0) {
secretQuery.tags = { $in: tagIds };
}
secrets = await Secret.find(secretQuery).populate("tags");
}
// TODO(akhilmhdh) - secret-imp change this to org type
let importedSecrets: any[] = [];
if (include_imports) {
@ -988,28 +909,6 @@ export const getSecrets = async (req: Request, res: Response) => {
);
}
const channel = getUserAgentType(req.headers["user-agent"]);
const readAction = await EELogService.createAction({
name: ACTION_READ_SECRETS,
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId as string),
secretIds: secrets.map((n: any) => n._id)
});
readAction &&
(await EELogService.createLog({
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId as string),
actions: [readAction],
channel,
ipAddress: req.realIP
}));
await EEAuditLogService.createAuditLog(
req.authData,
{
@ -1247,27 +1146,6 @@ export const updateSecrets = async (req: Request, res: Response) => {
// });
// }, 10000);
const updateAction = await EELogService.createAction({
name: ACTION_UPDATE_SECRETS,
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(key),
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
});
// (EE) create (audit) log
updateAction &&
(await EELogService.createLog({
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(key),
actions: [updateAction],
channel,
ipAddress: req.realIP
}));
// (EE) take a secret snapshot
// IMP(akhilmhdh): commented out due to unknown where the environment is
// await EESecretService.takeSecretSnapshot({
@ -1387,26 +1265,6 @@ export const deleteSecrets = async (req: Request, res: Response) => {
// workspaceId: new Types.ObjectId(key)
// })
// });
const deleteAction = await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(key),
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
});
// (EE) create (audit) log
deleteAction &&
(await EELogService.createLog({
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(key),
actions: [deleteAction],
channel,
ipAddress: req.realIP
}));
// (EE) take a secret snapshot
// IMP(akhilmhdh): Not sure how to take secretSnapshot

@ -1,306 +0,0 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import crypto from "crypto";
import bcrypt from "bcrypt";
import {
ServiceAccount,
ServiceAccountKey,
ServiceAccountOrganizationPermission,
ServiceAccountWorkspacePermission,
} from "../../models";
import {
CreateServiceAccountDto,
} from "../../interfaces/serviceAccounts/dto";
import { BadRequestError, ServiceAccountNotFoundError } from "../../utils/errors";
import { getSaltRounds } from "../../config";
/**
* Return service account tied to the request (service account) client
* @param req
* @param res
*/
export const getCurrentServiceAccount = async (req: Request, res: Response) => {
const serviceAccount = await ServiceAccount.findById(req.serviceAccount._id);
if (!serviceAccount) {
throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
}
return res.status(200).send({
serviceAccount,
});
}
/**
* Return service account with id [serviceAccountId]
* @param req
* @param res
*/
export const getServiceAccountById = async (req: Request, res: Response) => {
const { serviceAccountId } = req.params;
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
if (!serviceAccount) {
throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
}
return res.status(200).send({
serviceAccount,
});
}
/**
* 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 {
name,
organizationId,
publicKey,
expiresIn,
}: CreateServiceAccountDto = req.body;
let expiresAt;
if (expiresIn) {
expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
}
const secret = crypto.randomBytes(16).toString("base64");
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
// create service account
const serviceAccount = await new ServiceAccount({
name,
organization: new Types.ObjectId(organizationId),
user: req.user,
publicKey,
lastUsed: new Date(),
expiresAt,
secretHash,
}).save()
const serviceAccountObj = serviceAccount.toObject();
delete (serviceAccountObj as any).secretHash;
// provision default org-level permission for service account
await new ServiceAccountOrganizationPermission({
serviceAccount: serviceAccount._id,
}).save();
const secretId = Buffer.from(serviceAccount._id.toString(), "hex").toString("base64");
return res.status(200).send({
serviceAccountAccessKey: `sa.${secretId}.${secret}`,
serviceAccount: serviceAccountObj,
});
}
/**
* Change name of service account with id [serviceAccountId] to [name]
* @param req
* @param res
* @returns
*/
export const changeServiceAccountName = async (req: Request, res: Response) => {
const { serviceAccountId } = req.params;
const { name } = req.body;
const serviceAccount = await ServiceAccount.findOneAndUpdate(
{
_id: new Types.ObjectId(serviceAccountId),
},
{
name,
},
{
new: true,
}
);
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;
}
/**
* Return workspace-level permission for service account with id [serviceAccountId]
* @param req
* @param res
*/
export const getServiceAccountWorkspacePermissions = async (req: Request, res: Response) => {
const serviceAccountWorkspacePermissions = await ServiceAccountWorkspacePermission.find({
serviceAccount: req.serviceAccount._id,
}).populate("workspace");
return res.status(200).send({
serviceAccountWorkspacePermissions,
});
}
/**
* Add a workspace permission to service account with id [serviceAccountId]
* @param req
* @param res
*/
export const addServiceAccountWorkspacePermission = async (req: Request, res: Response) => {
const { serviceAccountId } = req.params;
const {
environment,
workspaceId,
read = false,
write = false,
encryptedKey,
nonce,
} = req.body;
if (!req.membership.workspace.environments.some((e: { name: string; slug: string }) => e.slug === environment)) {
return res.status(400).send({
message: "Failed to validate workspace environment",
});
}
const existingPermission = await ServiceAccountWorkspacePermission.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId),
environment,
});
if (existingPermission) throw BadRequestError({ message: "Failed to add workspace permission to service account due to already-existing " });
const serviceAccountWorkspacePermission = await new ServiceAccountWorkspacePermission({
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId),
environment,
read,
write,
}).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({
serviceAccountWorkspacePermission,
});
}
/**
* Delete workspace permission from service account with id [serviceAccountId]
* @param req
* @param res
*/
export const deleteServiceAccountWorkspacePermission = async (req: Request, res: Response) => {
const { serviceAccountWorkspacePermissionId } = req.params;
const serviceAccountWorkspacePermission = await ServiceAccountWorkspacePermission.findByIdAndDelete(serviceAccountWorkspacePermissionId);
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({
serviceAccountWorkspacePermission,
});
}
/**
* 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);
if (serviceAccount) {
await ServiceAccountKey.deleteMany({
serviceAccount: serviceAccount._id,
});
await ServiceAccountOrganizationPermission.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId),
});
await ServiceAccountWorkspacePermission.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId),
});
}
return res.status(200).send({
serviceAccount,
});
}
/**
* Return service account keys for service account with id [serviceAccountId]
* @param req
* @param res
* @returns
*/
export const getServiceAccountKeys = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string;
const serviceAccountKeys = await ServiceAccountKey.find({
serviceAccount: req.serviceAccount._id,
...(workspaceId ? { workspace: new Types.ObjectId(workspaceId) } : {}),
});
return res.status(200).send({
serviceAccountKeys,
});
}

@ -8,10 +8,8 @@ import { createToken, issueAuthTokens, validateProviderAuthToken } from "../../h
import { checkUserDevice } from "../../helpers/user";
import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services";
import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors";
import { ACTION_LOGIN, AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
import { AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
import { AuthMethod } from "../../models/user";
import { validateRequest } from "../../helpers/validation";
@ -215,19 +213,6 @@ export const login2 = async (req: Request, res: Response) => {
response.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
});
loginAction &&
(await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
return res.status(200).send(response);
}

@ -1,32 +0,0 @@
import { Request, Response } from "express";
import { Action } from "../../models";
import { ActionNotFoundError } from "../../../utils/errors";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/action";
export const getAction = async (req: Request, res: Response) => {
let action;
try {
const {
params: { actionId }
} = await validateRequest(reqValidator.GetActionV1, req);
action = await Action.findById(actionId).populate([
"payload.secretVersions.oldSecretVersion",
"payload.secretVersions.newSecretVersion"
]);
if (!action)
throw ActionNotFoundError({
message: "Failed to find action"
});
} catch (err) {
throw ActionNotFoundError({
message: "Failed to find action"
});
}
return res.status(200).send({
action
});
};

@ -4,7 +4,6 @@ import * as organizationsController from "./organizationsController";
import * as ssoController from "./ssoController";
import * as usersController from "./usersController";
import * as workspaceController from "./workspaceController";
import * as actionController from "./actionController";
import * as membershipController from "./membershipController";
import * as cloudProductsController from "./cloudProductsController";
import * as roleController from "./roleController";
@ -20,7 +19,6 @@ export {
ssoController,
usersController,
workspaceController,
actionController,
membershipController,
cloudProductsController,
roleController,

@ -17,7 +17,6 @@ import {
FolderVersion,
IPType,
ISecretVersion,
Log,
SecretSnapshot,
SecretVersion,
ServiceActor,
@ -38,7 +37,6 @@ import {
DeleteWorkspaceTrustedIpV1,
GetWorkspaceAuditLogActorFilterOptsV1,
GetWorkspaceAuditLogsV1,
GetWorkspaceLogsV1,
GetWorkspaceSecretSnapshotsCountV1,
GetWorkspaceSecretSnapshotsV1,
GetWorkspaceTrustedIpsV1,
@ -563,112 +561,6 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
});
};
/**
* Return (audit) logs for workspace with id [workspaceId]
* @param req
* @param res
* @returns
*/
export const getWorkspaceLogs = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return project (audit) logs'
#swagger.description = 'Return project (audit) logs'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of project",
"required": true,
"type": "string"
}
#swagger.parameters['userId'] = {
"description": "ID of project member",
"required": false,
"type": "string"
}
#swagger.parameters['offset'] = {
"description": "Number of logs to skip",
"required": false,
"type": "string"
}
#swagger.parameters['limit'] = {
"description": "Maximum number of logs to return",
"required": false,
"type": "string"
}
#swagger.parameters['sortBy'] = {
"description": "Order to sort the logs by",
"schema": {
"type": "string",
"@enum": ["oldest", "recent"]
},
"required": false
}
#swagger.parameters['actionNames'] = {
"description": "Names of log actions (comma-separated)",
"required": false,
"type": "string"
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
"type": "object",
"properties": {
"logs": {
"type": "array",
"items": {
$ref: "#/components/schemas/Log"
},
"description": "Project logs"
}
}
}
}
}
}
*/
const {
query: { limit, offset, userId, sortBy, actionNames },
params: { workspaceId }
} = await validateRequest(GetWorkspaceLogsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs
);
const logs = await Log.find({
workspace: workspaceId,
...(userId ? { user: userId } : {}),
...(actionNames
? {
actionNames: {
$in: actionNames.split(",")
}
}
: {})
})
.sort({ createdAt: sortBy === "recent" ? -1 : 1 })
.skip(offset)
.limit(limit)
.populate("actions")
.populate("user serviceAccount serviceTokenData");
return res.status(200).send({
logs
});
};
/**
* Return audit logs for workspace with id [workspaceId]
* @param req

@ -1,195 +0,0 @@
import { Types } from "mongoose";
import { Action } from "../models";
import {
getLatestNSecretSecretVersionIds,
getLatestSecretVersionIds,
} from "../helpers/secretVersion";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
} from "../../variables";
/**
* Create an (audit) action for updating secrets
* @param {Object} obj
* @param {String} obj.name - name of action
* @param {Types.ObjectId} obj.secretIds - ids of relevant secrets
* @returns {Action} action - new action
*/
const createActionUpdateSecret = async ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[];
}) => {
const latestSecretVersions = (await getLatestNSecretSecretVersionIds({
secretIds,
n: 2,
}))
.map((s) => ({
oldSecretVersion: s.versions[0]._id,
newSecretVersion: s.versions[1]._id,
}));
const action = await new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions,
},
}).save();
return action;
}
/**
* Create an (audit) action for creating, reading, and deleting
* secrets
* @param {Object} obj
* @param {String} obj.name - name of action
* @param {Types.ObjectId} obj.secretIds - ids of relevant secrets
* @returns {Action} action - new action
*/
const createActionSecret = async ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[];
}) => {
// case: action is adding, deleting, or reading secrets
// -> add new secret versions
const latestSecretVersions = (await getLatestSecretVersionIds({
secretIds,
}))
.map((s) => ({
newSecretVersion: s.versionId,
}));
const action = await new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions,
},
}).save();
return action;
}
/**
* Create an (audit) action for client with id [userId],
* [serviceAccountId], or [serviceTokenDataId]
* @param {Object} obj
* @param {String} obj.name - name of action
* @param {String} obj.userId - id of user associated with action
* @returns
*/
const createActionClient = ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
}) => {
const action = new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
}).save();
return action;
}
/**
* Create an (audit) action.
* @param {Object} obj
* @param {Object} obj.name - name of action
* @param {Types.ObjectId} obj.userId - id of user associated with action
* @param {Types.ObjectId} obj.workspaceId - id of workspace associated with action
* @param {Types.ObjectId[]} obj.secretIds - ids of secrets associated with action
*/
const createActionHelper = async ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId?: Types.ObjectId;
secretIds?: Types.ObjectId[];
}) => {
let action;
switch (name) {
case ACTION_LOGIN:
case ACTION_LOGOUT:
action = await createActionClient({
name,
userId,
});
break;
case ACTION_ADD_SECRETS:
case ACTION_READ_SECRETS:
case ACTION_DELETE_SECRETS:
if (!workspaceId || !secretIds) throw new Error("Missing required params workspace id or secret ids to create action secret");
action = await createActionSecret({
name,
userId,
workspaceId,
secretIds,
});
break;
case ACTION_UPDATE_SECRETS:
if (!workspaceId || !secretIds) throw new Error("Missing required params workspace id or secret ids to create action secret");
action = await createActionUpdateSecret({
name,
userId,
workspaceId,
secretIds,
});
break;
}
return action;
}
export {
createActionHelper,
};

@ -1,50 +0,0 @@
import { Types } from "mongoose";
import {
IAction,
Log,
} from "../models";
/**
* Create an (audit) log
* @param {Object} obj
* @param {Types.ObjectId} obj.userId - id of user associated with the log
* @param {Types.ObjectId} obj.workspaceId - id of workspace associated with the log
* @param {IAction[]} obj.actions - actions to include in log
* @param {String} obj.channel - channel (web/cli/auto) associated with the log
* @param {String} obj.ipAddress - ip address associated with the log
* @returns {Log} log - new audit log
*/
const createLogHelper = async ({
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
actions,
channel,
ipAddress,
}: {
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId?: Types.ObjectId;
actions: IAction[];
channel: string;
ipAddress: string;
}) => {
const log = await new Log({
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId ?? undefined,
actionNames: actions.map((a) => a.name),
actions,
channel,
ipAddress,
}).save();
return log;
}
export {
createLogHelper,
}

@ -1,69 +0,0 @@
import { Schema, Types, model } from "mongoose";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
} from "../../variables";
export interface IAction {
name: string;
user?: Types.ObjectId,
serviceAccount?: Types.ObjectId,
serviceTokenData?: Types.ObjectId,
workspace?: Types.ObjectId,
payload?: {
secretVersions?: Types.ObjectId[]
}
}
const actionSchema = new Schema<IAction>(
{
name: {
type: String,
required: true,
enum: [
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS,
],
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount",
},
serviceTokenData: {
type: Schema.Types.ObjectId,
ref: "ServiceTokenData",
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
},
payload: {
secretVersions: [{
oldSecretVersion: {
type: Schema.Types.ObjectId,
ref: "SecretVersion",
},
newSecretVersion: {
type: Schema.Types.ObjectId,
ref: "SecretVersion",
},
}],
},
}, {
timestamps: true,
}
);
export const Action = model<IAction>("Action", actionSchema);

@ -1,9 +1,7 @@
export * from "./secretSnapshot";
export * from "./secretVersion";
export * from "./folderVersion";
export * from "./log";
export * from "./role";
export * from "./action";
export * from "./ssoConfig";
export * from "./trustedIp";
export * from "./auditLog";

@ -1,72 +0,0 @@
import { Schema, Types, model } from "mongoose";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
} from "../../variables";
export interface ILog {
_id: Types.ObjectId;
user?: Types.ObjectId;
serviceAccount?: Types.ObjectId;
serviceTokenData?: Types.ObjectId;
workspace?: Types.ObjectId;
actionNames: string[];
actions: Types.ObjectId[];
channel: string;
ipAddress?: string;
}
const logSchema = new Schema<ILog>(
{
user: {
type: Schema.Types.ObjectId,
ref: "User",
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount",
},
serviceTokenData: {
type: Schema.Types.ObjectId,
ref: "ServiceTokenData",
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
},
actionNames: {
type: [String],
enum: [
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS,
],
required: true,
},
actions: [{
type: Schema.Types.ObjectId,
ref: "Action",
required: true,
}],
channel: {
type: String,
enum: ["web", "cli", "auto", "k8-operator", "other"],
required: true,
},
ipAddress: {
type: String,
},
},
{
timestamps: true,
}
);
export const Log = model<ILog>("Log", logSchema);

@ -1,8 +0,0 @@
import express from "express";
const router = express.Router();
import { actionController } from "../../controllers/v1";
// TODO: put into action controller
router.get("/:actionId", actionController.getAction);
export default router;

@ -4,7 +4,6 @@ import organizations from "./organizations";
import sso from "./sso";
import users from "./users";
import workspace from "./workspace";
import action from "./action";
import cloudProducts from "./cloudProducts";
import secretScanning from "./secretScanning";
import roles from "./role";
@ -20,7 +19,6 @@ export {
sso,
users,
workspace,
action,
cloudProducts,
secretScanning,
roles,

@ -28,14 +28,6 @@ router.post(
workspaceController.rollbackWorkspaceSecretSnapshot
);
router.get(
"/:workspaceId/logs",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
workspaceController.getWorkspaceLogs
);
router.get(
"/:workspaceId/audit-logs",
requireAuth({

@ -1,91 +0,0 @@
import { Types } from "mongoose";
import {
IAction,
} from "../models";
import {
createLogHelper,
} from "../helpers/log";
import {
createActionHelper,
} from "../helpers/action";
import EELicenseService from "./EELicenseService";
/**
* Class to handle Enterprise Edition log actions
*/
class EELogService {
/**
* Create an (audit) log
* @param {Object} obj
* @param {String} obj.userId - id of user associated with the log
* @param {String} obj.workspaceId - id of workspace associated with the log
* @param {Action} obj.actions - actions to include in log
* @param {String} obj.channel - channel (web/cli/auto) associated with the log
* @param {String} obj.ipAddress - ip address associated with the log
* @returns {Log} log - new audit log
*/
static async createLog({
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
actions,
channel,
ipAddress,
}: {
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId?: Types.ObjectId;
actions: IAction[];
channel: string;
ipAddress: string;
}) {
if (!EELicenseService.isLicenseValid) return null;
return await createLogHelper({
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
actions,
channel,
ipAddress,
})
}
/**
* Create an (audit) action
* @param {Object} obj
* @param {String} obj.name - name of action
* @param {Types.ObjectId} obj.userId - id of user associated with the action
* @param {Types.ObjectId} obj.workspaceId - id of workspace associated with the action
* @param {ObjectId[]} obj.secretIds - ids of secrets associated with the action
* @returns {Action} action - new action
*/
static async createAction({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId?: Types.ObjectId;
secretIds?: Types.ObjectId[];
}) {
return await createActionHelper({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
});
}
}
export default EELogService;

@ -1,13 +1,11 @@
import EELicenseService from "./EELicenseService";
import EESecretService from "./EESecretService";
import EELogService from "./EELogService";
import EEAuditLogService from "./EEAuditLogService";
import GithubSecretScanningService from "./GithubSecretScanning/GithubSecretScanningService"
export {
EELicenseService,
EESecretService,
EELogService,
EEAuditLogService,
GithubSecretScanningService
}

@ -23,13 +23,11 @@ import {
Workspace
} from "../models";
import {
Action,
AuditLog,
FolderVersion,
GitAppInstallationSession,
GitAppOrganizationInstallation,
GitRisks,
Log,
Role,
SSOConfig,
SecretApprovalPolicy,
@ -288,18 +286,6 @@ export const deleteOrganization = async ({
}
});
await Log.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Action.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretApprovalPolicy.deleteMany({
workspace: {
$in: workspaceIds

@ -1,12 +1,8 @@
import { Types } from "mongoose";
import { ISecret, Secret } from "../models";
import { EELogService, EESecretService } from "../ee/services";
import { IAction, SecretVersion } from "../ee/models";
import { EESecretService } from "../ee/services";
import { SecretVersion } from "../ee/models";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
SECRET_PERSONAL,
@ -320,7 +316,6 @@ export const v2PushSecrets = async ({
ipAddress: string;
}): Promise<void> => {
// TODO: clean up function and fix up types
const actions: IAction[] = [];
// construct useful data structures
const oldSecrets = await getSecrets({
@ -356,15 +351,6 @@ export const v2PushSecrets = async ({
await EESecretService.markDeletedSecretVersions({
secretIds: toDelete,
});
const deleteAction = await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(userId),
secretIds: toDelete,
});
deleteAction && actions.push(deleteAction);
}
const toUpdate = oldSecrets.filter((s) => {
@ -451,15 +437,6 @@ export const v2PushSecrets = async ({
};
}),
});
const updateAction = await EELogService.createAction({
name: ACTION_UPDATE_SECRETS,
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId),
secretIds: toUpdate.map((u) => u._id),
});
updateAction && actions.push(updateAction);
}
// handle adding new secrets
@ -494,14 +471,6 @@ export const v2PushSecrets = async ({
});
}),
});
const addAction = await EELogService.createAction({
name: ACTION_ADD_SECRETS,
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId),
secretIds: newSecrets.map((n) => n._id),
});
addAction && actions.push(addAction);
}
// (EE) take a secret snapshot
@ -509,17 +478,6 @@ export const v2PushSecrets = async ({
workspaceId: new Types.ObjectId(workspaceId),
environment,
});
// (EE) create (audit) log
if (actions.length > 0) {
await EELogService.createLog({
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId),
actions,
channel,
ipAddress,
});
}
};
/**
@ -589,22 +547,6 @@ export const pullSecrets = async ({
environment,
});
const readAction = await EELogService.createAction({
name: ACTION_READ_SECRETS,
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId),
secretIds: secrets.map((n: any) => n._id),
});
readAction &&
(await EELogService.createLog({
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId),
actions: [readAction],
channel,
ipAddress,
}));
return secrets;
};

@ -29,10 +29,6 @@ import {
UnauthorizedRequestError
} from "../utils/errors";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
@ -48,8 +44,8 @@ import {
} from "../utils/crypto";
import { TelemetryService } from "../services";
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
import { EEAuditLogService, EELogService, EESecretService } from "../ee/services";
import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/authn/helpers";
import { EEAuditLogService, EESecretService } from "../ee/services";
import { getAuthDataPayloadUserObj } from "../utils/authn/helpers";
import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService";
import picomatch from "picomatch";
import path from "path";
@ -478,23 +474,6 @@ export const createSecretHelper = async ({
secretVersions: [secretVersion]
});
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_ADD_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: [secret._id]
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog(
authData,
{
@ -597,23 +576,6 @@ export const getSecretsHelper = async ({
.lean()
);
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_READ_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: secrets.map((secret) => secret._id)
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog(
authData,
{
@ -725,23 +687,6 @@ export const getSecretHelper = async ({
if (!secret) throw SecretNotFoundError();
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_READ_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: [secret._id]
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog(
authData,
{
@ -945,23 +890,6 @@ export const updateSecretHelper = async ({
secretVersions: [secretVersion]
});
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_UPDATE_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: [secret._id]
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog(
authData,
{
@ -1089,23 +1017,6 @@ export const deleteSecretHelper = async ({
secretIds: secrets.map((secret) => secret._id)
});
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: secrets.map((secret) => secret._id)
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog(
authData,
{
@ -1769,22 +1680,6 @@ export const deleteSecretBatchHelper = async ({
secretIds: deletedSecrets.map((secret) => secret._id)
});
const action = await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: deletedSecrets.map((secret) => secret._id)
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog(
authData,
{

@ -10,10 +10,6 @@ import {
User,
UserAction
} from "../models";
import {
Action,
Log
} from "../ee/models";
import { sendMail } from "./nodemailer";
import {
InternalServerError,
@ -247,14 +243,6 @@ export const deleteUser = async ({
user: user._id
});
await Action.deleteMany({
user: user._id
});
await Log.deleteMany({
user: user._id
});
await TokenVersion.deleteMany({
user: user._id
});

@ -19,11 +19,9 @@ import {
Workspace
} from "../models";
import {
Action,
AuditLog,
FolderVersion,
IPType,
Log,
SecretApprovalPolicy,
SecretApprovalRequest,
SecretSnapshot,
@ -193,14 +191,6 @@ export const deleteWorkspace = async ({
workspace: workspace._id
});
await Log.deleteMany({
workspace: workspace._id
});
await Action.deleteMany({
workspace: workspace._id
});
await SecretApprovalPolicy.deleteMany({
workspace: workspace._id
});

@ -18,7 +18,6 @@ const swaggerFile = require("../spec.json");
// eslint-disable-next-line @typescript-eslint/no-var-requires
import { apiLimiter } from "./helpers/rateLimiter";
import {
action as eeActionRouter,
cloudProducts as eeCloudProductsRouter,
organizations as eeOrganizationsRouter,
sso as eeSSORouter,
@ -193,7 +192,6 @@ const main = async () => {
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
app.use("/api/v1/users", eeUsersRouter);
app.use("/api/v1/workspace", eeWorkspaceRouter);
app.use("/api/v1/action", eeActionRouter);
app.use("/api/v1/organizations", eeOrganizationsRouter);
app.use("/api/v1/sso", eeSSORouter);
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
@ -240,14 +238,13 @@ const main = async () => {
app.use("/api/v2/secret", v2SecretRouter); // deprecate
app.use("/api/v2/secrets", v2SecretsRouter); // note: in the process of moving to v3/secrets
app.use("/api/v2/service-token", v2ServiceTokenDataRouter);
// app.use("/api/v2/service-accounts", v2ServiceAccountsRouter); // new
// v3 routes (experimental)
app.use("/api/v3/auth", v3AuthRouter);
app.use("/api/v3/secrets", v3SecretsRouter);
app.use("/api/v3/workspaces", v3WorkspacesRouter);
app.use("/api/v3/signup", v3SignupRouter);
app.use("/api/v3/users", v3UsersRouter);
app.use("/api/v3/us", v3UsersRouter);
// api docs
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));

@ -1,7 +0,0 @@
interface AddServiceAccountPermissionDto {
name: string;
workspaceId?: string;
environment?: string;
}
export default AddServiceAccountPermissionDto;

@ -1,8 +0,0 @@
interface CreateServiceAccountDto {
organizationId: string;
name: string;
publicKey: string;
expiresIn: number;
}
export default CreateServiceAccountDto;

@ -1,7 +0,0 @@
import CreateServiceAccountDto from "./CreateServiceAccountDto";
import AddServiceAccountPermissionDto from "./AddServiceAccountPermissionDto";
export {
CreateServiceAccountDto,
AddServiceAccountPermissionDto,
}

@ -10,8 +10,6 @@ import requireIntegrationAuth from "./requireIntegrationAuth";
import requireIntegrationAuthorizationAuth from "./requireIntegrationAuthorizationAuth";
import requireServiceTokenAuth from "./requireServiceTokenAuth";
import requireServiceTokenDataAuth from "./requireServiceTokenDataAuth";
import requireServiceAccountAuth from "./requireServiceAccountAuth";
import requireServiceAccountWorkspacePermissionAuth from "./requireServiceAccountWorkspacePermissionAuth";
import requireSecretAuth from "./requireSecretAuth";
import requireSecretsAuth from "./requireSecretsAuth";
import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled";
@ -32,8 +30,6 @@ export {
requireIntegrationAuthorizationAuth,
requireServiceTokenAuth,
requireServiceTokenDataAuth,
requireServiceAccountAuth,
requireServiceAccountWorkspacePermissionAuth,
requireSecretAuth,
requireSecretsAuth,
requireBlindIndicesEnabled,

@ -1,31 +0,0 @@
import { NextFunction, Request, Response } from "express";
import { Types } from "mongoose";
import { validateClientForServiceAccount } from "../validation";
type req = "params" | "body" | "query";
const requireServiceAccountAuth = ({
acceptedRoles,
acceptedStatuses,
locationServiceAccountId = "params",
requiredPermissions = [],
}: {
acceptedRoles: string[];
acceptedStatuses: string[];
locationServiceAccountId?: req;
requiredPermissions?: string[];
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const serviceAccountId = req[locationServiceAccountId].serviceAccountId;
req.serviceAccount = await validateClientForServiceAccount({
authData: req.authData,
serviceAccountId: new Types.ObjectId(serviceAccountId),
requiredPermissions,
});
next();
}
}
export default requireServiceAccountAuth;

@ -1,52 +0,0 @@
import { NextFunction, Request, Response } from "express";
import { ServiceAccount, ServiceAccountWorkspacePermission } from "../models";
import {
ServiceAccountNotFoundError,
} from "../utils/errors";
import {
validateMembershipOrg,
} from "../helpers/membershipOrg";
type req = "params" | "body" | "query";
const requireServiceAccountWorkspacePermissionAuth = ({
acceptedRoles,
acceptedStatuses,
location = "params",
}: {
acceptedRoles: Array<"owner" | "admin" | "member">;
acceptedStatuses: Array<"invited" | "accepted">;
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const serviceAccountWorkspacePermissionId = req[location].serviceAccountWorkspacePermissionId;
const serviceAccountWorkspacePermission = await ServiceAccountWorkspacePermission.findById(serviceAccountWorkspacePermissionId);
if (!serviceAccountWorkspacePermission) {
return next(ServiceAccountNotFoundError({ message: "Failed to locate Service Account workspace permission" }));
}
const serviceAccount = await ServiceAccount.findById(serviceAccountWorkspacePermission.serviceAccount);
if (!serviceAccount) {
return next(ServiceAccountNotFoundError({ message: "Failed to locate Service Account" }));
}
if (serviceAccount.user.toString() !== req.user.id.toString()) {
// case: creator of the service account is different from
// the user on the request -> apply middleware role/status validation
await validateMembershipOrg({
userId: req.user._id,
organizationId: serviceAccount.organization,
acceptedRoles,
acceptedStatuses,
});
}
req.serviceAccount = serviceAccount;
next();
}
}
export default requireServiceAccountWorkspacePermissionAuth;

@ -15,10 +15,6 @@ export * from "./folder";
export * from "./secretImports";
export * from "./secretBlindIndexData";
export * from "./serviceToken"; // TODO: deprecate
export * from "./serviceAccount"; // TODO: deprecate
export * from "./serviceAccountKey"; // TODO: deprecate
export * from "./serviceAccountOrganizationPermission"; // TODO: deprecate
export * from "./serviceAccountWorkspacePermission"; // TODO: deprecate
export * from "./tokenData";
export * from "./user";
export * from "./userAction";

@ -1,51 +0,0 @@
import { Document, Schema, Types, model } from "mongoose";
export interface IServiceAccount extends Document {
_id: Types.ObjectId;
name: string;
organization: Types.ObjectId;
user: Types.ObjectId;
publicKey: string;
lastUsed: Date;
expiresAt: Date;
secretHash: string;
}
const serviceAccountSchema = new Schema<IServiceAccount>(
{
name: {
type: String,
required: true,
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization",
required: true,
},
user: { // user who created the service account
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
publicKey: {
type: String,
required: true,
},
lastUsed: {
type: Date,
},
expiresAt: {
type: Date,
},
secretHash: {
type: String,
required: true,
select: false,
},
},
{
timestamps: true,
}
);
export const ServiceAccount = model<IServiceAccount>("ServiceAccount", serviceAccountSchema);

@ -1,42 +0,0 @@
import { Schema, Types, model } from "mongoose";
export interface IServiceAccountKey {
_id: Types.ObjectId;
encryptedKey: string;
nonce: string;
sender: Types.ObjectId;
serviceAccount: Types.ObjectId;
workspace: Types.ObjectId;
}
const serviceAccountKeySchema = new Schema<IServiceAccountKey>(
{
encryptedKey: {
type: String,
required: true,
},
nonce: {
type: String,
required: true,
},
sender: {
type: Schema.Types.ObjectId,
required: true,
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount",
required: true,
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
},
{
timestamps: true,
}
);
export const ServiceAccountKey = model<IServiceAccountKey>("ServiceAccountKey", serviceAccountKeySchema);

@ -1,21 +0,0 @@
import { Document, Schema, Types, model } from "mongoose";
export interface IServiceAccountOrganizationPermission extends Document {
_id: Types.ObjectId;
serviceAccount: Types.ObjectId;
}
const serviceAccountOrganizationPermissionSchema = new Schema<IServiceAccountOrganizationPermission>(
{
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount",
required: true,
},
},
{
timestamps: true,
}
);
export const ServiceAccountOrganizationPermission = model<IServiceAccountOrganizationPermission>("ServiceAccountOrganizationPermission", serviceAccountOrganizationPermissionSchema);

@ -1,42 +0,0 @@
import { Document, Schema, Types, model } from "mongoose";
export interface IServiceAccountWorkspacePermission extends Document {
_id: Types.ObjectId;
serviceAccount: Types.ObjectId;
workspace: Types.ObjectId;
environment: string;
read: boolean;
write: boolean;
}
const serviceAccountWorkspacePermissionSchema = new Schema<IServiceAccountWorkspacePermission>(
{
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount",
required: true,
},
workspace:{
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
environment: {
type: String,
required: true,
},
read: {
type: Boolean,
default: false,
},
write: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
}
);
export const ServiceAccountWorkspacePermission = model<IServiceAccountWorkspacePermission>("ServiceAccountWorkspacePermission", serviceAccountWorkspacePermissionSchema);

@ -4,7 +4,6 @@ import membership from "./membership";
import organizations from "./organizations";
import secret from "./secret"; // deprecated
import secrets from "./secrets";
import serviceAccounts from "./serviceAccounts";
import serviceTokenData from "./serviceTokenData";
import signup from "./signup";
import tags from "./tags";
@ -20,7 +19,6 @@ export {
secret,
secrets,
serviceTokenData,
serviceAccounts,
environment,
tags,
membership

@ -1,10 +1,7 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
requireOrganizationAuth
} from "../../middleware";
import { ACCEPTED, ADMIN, AuthMode } from "../../variables";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
import { organizationsController } from "../../controllers/v2";
// TODO: /POST to create membership
@ -41,19 +38,6 @@ router.get(
organizationsController.getOrganizationWorkspaces
);
router.get(
// TODO endpoint: deprecate service accounts
"/:organizationId/service-accounts",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireOrganizationAuth({
acceptedRoles: [ADMIN],
acceptedStatuses: [ACCEPTED]
}),
organizationsController.getOrganizationServiceAccounts
);
router.post(
"/",
requireAuth({

@ -1,161 +0,0 @@
import express from "express";
const router = express.Router();
// TODO endpoint: deprecate all
// import {
// requireAuth,
// requireOrganizationAuth,
// requireServiceAccountAuth,
// requireServiceAccountWorkspacePermissionAuth,
// requireWorkspaceAuth,
// validateRequest,
// } from "../../middleware";
// import { body, param, query } from "express-validator";
// import {
// ACCEPTED,
// ADMIN,
// MEMBER,
// OWNER,
// AuthMode
// } from "../../variables";
// import { serviceAccountsController } from "../../controllers/v2";
// router.get( // TODO: check
// "/me",
// requireAuth({
// acceptedAuthModes: [AUTH_MODE_SERVICE_ACCOUNT],
// }),
// serviceAccountsController.getCurrentServiceAccount
// );
// router.get(
// "/:serviceAccountId",
// param("serviceAccountId").exists().isString().trim(),
// requireAuth({
// acceptedAuthModes: [AUTH_MODE_JWT],
// }),
// requireServiceAccountAuth({
// acceptedRoles: [OWNER, ADMIN],
// acceptedStatuses: [ACCEPTED],
// }),
// serviceAccountsController.getServiceAccountById
// );
// router.post(
// "/",
// body("organizationId").exists().isString().trim(),
// body("name").exists().isString().trim(),
// body("publicKey").exists().isString().trim(),
// body("expiresIn").isNumeric(), // measured in ms
// validateRequest,
// requireAuth({
// acceptedAuthModes: [AUTH_MODE_JWT],
// }),
// requireOrganizationAuth({
// acceptedRoles: [OWNER, ADMIN, MEMBER],
// acceptedStatuses: [ACCEPTED],
// locationOrganizationId: "body",
// }),
// serviceAccountsController.createServiceAccount
// );
// router.patch(
// "/:serviceAccountId/name",
// param("serviceAccountId").exists().isString().trim(),
// validateRequest,
// requireAuth({
// acceptedAuthModes: [AUTH_MODE_JWT],
// }),
// requireServiceAccountAuth({
// acceptedRoles: [OWNER, ADMIN],
// acceptedStatuses: [ACCEPTED],
// }),
// serviceAccountsController.changeServiceAccountName
// );
// router.delete(
// "/:serviceAccountId",
// param("serviceAccountId").exists().isString().trim(),
// validateRequest,
// requireAuth({
// acceptedAuthModes: [AUTH_MODE_JWT],
// }),
// requireServiceAccountAuth({
// acceptedRoles: [OWNER, ADMIN],
// acceptedStatuses: [ACCEPTED],
// }),
// serviceAccountsController.deleteServiceAccount
// );
// router.get(
// "/:serviceAccountId/permissions/workspace",
// param("serviceAccountId").exists().isString().trim(),
// validateRequest,
// requireAuth({
// acceptedAuthModes: [AUTH_MODE_JWT],
// }),
// requireServiceAccountAuth({
// acceptedRoles: [OWNER, ADMIN],
// acceptedStatuses: [ACCEPTED],
// }),
// serviceAccountsController.getServiceAccountWorkspacePermissions
// );
// router.post(
// "/:serviceAccountId/permissions/workspace",
// param("serviceAccountId").exists().isString().trim(),
// body("workspaceId").exists().isString().notEmpty(),
// body("environment").exists().isString().notEmpty(),
// body("read").isBoolean().optional(),
// body("write").isBoolean().optional(),
// body("encryptedKey").exists().isString().notEmpty(),
// body("nonce").exists().isString().notEmpty(),
// validateRequest,
// requireAuth({
// acceptedAuthModes: [AUTH_MODE_JWT],
// }),
// requireServiceAccountAuth({
// acceptedRoles: [OWNER, ADMIN],
// acceptedStatuses: [ACCEPTED],
// }),
// requireWorkspaceAuth({
// acceptedRoles: [ADMIN, MEMBER],
// locationWorkspaceId: "body",
// }),
// serviceAccountsController.addServiceAccountWorkspacePermission
// );
// router.delete(
// "/:serviceAccountId/permissions/workspace/:serviceAccountWorkspacePermissionId",
// param("serviceAccountId").exists().isString().trim(),
// param("serviceAccountWorkspacePermissionId").exists().isString().trim(),
// validateRequest,
// requireAuth({
// acceptedAuthModes: [AUTH_MODE_JWT],
// }),
// requireServiceAccountAuth({
// acceptedRoles: [OWNER, ADMIN],
// acceptedStatuses: [ACCEPTED],
// }),
// requireServiceAccountWorkspacePermissionAuth({
// acceptedRoles: [OWNER, ADMIN],
// acceptedStatuses: [ACCEPTED],
// }),
// serviceAccountsController.deleteServiceAccountWorkspacePermission
// );
// router.get(
// "/:serviceAccountId/keys",
// query("workspaceId").optional().isString(),
// requireAuth({
// acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT],
// }),
// requireServiceAccountAuth({
// acceptedRoles: [OWNER, ADMIN],
// acceptedStatuses: [ACCEPTED],
// }),
// serviceAccountsController.getServiceAccountKeys
// );
export default router;

@ -8,7 +8,6 @@ import {
getTelemetryEnabled,
} from "../config";
import {
ServiceAccount,
ServiceTokenData,
User,
} from "../models";
@ -56,16 +55,11 @@ class Telemetry {
let distinctId = "";
if (authData.authPayload instanceof User) {
distinctId = authData.authPayload.email;
} else if (authData.authPayload instanceof ServiceAccount) {
distinctId = `sa.${authData.authPayload._id.toString()}`;
} else if (authData.authPayload instanceof ServiceTokenData) {
if (authData.authPayload.user) {
const user = await User.findById(authData.authPayload.user, "email");
if (!user) throw AccountNotFoundError();
distinctId = user.email;
} else if (authData.authPayload.serviceAccount) {
distinctId = distinctId = `sa.${authData.authPayload.serviceAccount.toString()}`;
}
}

@ -29,7 +29,6 @@ declare global {
secrets: any;
secretSnapshot: any;
serviceToken: any;
serviceAccount: any;
accessToken: any;
accessId: any;
serviceTokenData: any;

@ -1,6 +1,5 @@
import { AuthData } from "../../../interfaces/middleware";
import {
ServiceAccount,
ServiceTokenData,
ServiceTokenDataV3,
User
@ -16,10 +15,6 @@ import {
return { userId: authData.authPayload._id };
}
if (authData.authPayload instanceof ServiceAccount) {
return { serviceAccountId: authData.authPayload._id };
}
if (authData.authPayload instanceof ServiceTokenData) {
return { serviceTokenDataId: authData.authPayload._id };
}
@ -39,10 +34,6 @@ export const getAuthDataPayloadUserObj = (authData: AuthData) => {
return { user: authData.authPayload._id };
}
if (authData.authPayload instanceof ServiceAccount) {
return { user: authData.authPayload.user };
}
if (authData.authPayload instanceof ServiceTokenData) {
return { user: authData.authPayload.user };
}

@ -182,16 +182,6 @@ export const SecretSnapshotNotFoundError = (error?: Partial<RequestErrorContext>
stack: error?.stack,
});
//* ----->[ACTION ERRORS]<-----
export const ActionNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 404,
type: error?.type ?? "action_not_found_error",
message: error?.message ?? "The requested action was not found",
context: error?.context,
stack: error?.stack,
});
//* ----->[SERVICE TOKEN DATA ERRORS]<-----
export const ServiceTokenDataNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
@ -212,25 +202,6 @@ export const APIKeyDataNotFoundError = (error?: Partial<RequestErrorContext>) =>
stack: error?.stack,
});
//* ----->[SERVICE_ACCOUNT ERRORS]<-----
export const ServiceAccountNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 404,
type: error?.type ?? "service_account_not_found_error",
message: error?.message ?? "The requested service account was not found",
context: error?.context,
stack: error?.stack,
});
export const ServiceAccountKeyNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 404,
type: error?.type ?? "service_account_key_not_found_error",
message: error?.message ?? "The requested service account key was not found",
context: error?.context,
stack: error?.stack,
})
export const BotNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 404,

@ -7,7 +7,6 @@ export * from "./membership";
export * from "./membershipOrg";
export * from "./organization";
export * from "./secrets";
export * from "./serviceAccount";
export * from "./serviceTokenData";
export * from "./serviceTokenDataV3";
export * from "./apiKeyDataV3";

@ -1,227 +0,0 @@
import _ from "lodash";
import { Types } from "mongoose";
import {
IOrganization,
ISecret,
IServiceAccount,
IUser,
ServiceAccount,
ServiceAccountWorkspacePermission,
} from "../models";
import { validateUserClientForServiceAccount } from "./user";
import {
BadRequestError,
ServiceAccountNotFoundError,
UnauthorizedRequestError,
} from "../utils/errors";
import {
PERMISSION_READ_SECRETS,
PERMISSION_WRITE_SECRETS,
} from "../variables";
import { AuthData } from "../interfaces/middleware";
import { ActorType } from "../ee/models";
export const validateClientForServiceAccount = async ({
authData,
serviceAccountId,
requiredPermissions,
}: {
authData: AuthData;
serviceAccountId: Types.ObjectId;
requiredPermissions?: string[];
}) => {
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
if (!serviceAccount) {
throw ServiceAccountNotFoundError({
message: "Failed to find service account",
});
}
switch (authData.actor.type) {
case ActorType.USER:
await validateUserClientForServiceAccount({
user: authData.authPayload as IUser,
serviceAccount,
requiredPermissions,
});
return serviceAccount;
case ActorType.SERVICE:
throw UnauthorizedRequestError({
message: "Failed service token authorization for service account resource",
});
}
}
/**
* Validate that service account (client) can access workspace
* with id [workspaceId] and its environment [environment] with required permissions
* [requiredPermissions]
* @param {Object} obj
* @param {ServiceAccount} obj.serviceAccount - service account client
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
* @param {String} environment - (optional) environment in workspace to validate against
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
*/
export const validateServiceAccountClientForWorkspace = async ({
serviceAccount,
workspaceId,
environment,
requiredPermissions,
}: {
serviceAccount: IServiceAccount;
workspaceId: Types.ObjectId;
environment?: string;
requiredPermissions?: string[];
}) => {
if (environment) {
// case: environment specified ->
// evaluate service account authorization for workspace
// in the context of a specific environment [environment]
const permission = await ServiceAccountWorkspacePermission.findOne({
serviceAccount,
workspace: new Types.ObjectId(workspaceId),
environment,
});
if (!permission) throw UnauthorizedRequestError({
message: "Failed service account authorization for the given workspace environment",
});
let runningIsDisallowed = false;
requiredPermissions?.forEach((requiredPermission: string) => {
switch (requiredPermission) {
case PERMISSION_READ_SECRETS:
if (!permission.read) runningIsDisallowed = true;
break;
case PERMISSION_WRITE_SECRETS:
if (!permission.write) runningIsDisallowed = true;
break;
default:
break;
}
if (runningIsDisallowed) {
throw UnauthorizedRequestError({
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`,
});
}
});
} else {
// case: no environment specified ->
// evaluate service account authorization for workspace
// without need of environment [environment]
const permission = await ServiceAccountWorkspacePermission.findOne({
serviceAccount,
workspace: new Types.ObjectId(workspaceId),
});
if (!permission) throw UnauthorizedRequestError({
message: "Failed service account authorization for the given workspace",
});
}
}
/**
* Validate that service account (client) can access secrets
* with required permissions [requiredPermissions]
* @param {Object} obj
* @param {ServiceAccount} obj.serviceAccount - service account client
* @param {Secret[]} secrets - secrets to validate against
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
*/
export const validateServiceAccountClientForSecrets = async ({
serviceAccount,
secrets,
requiredPermissions,
}: {
serviceAccount: IServiceAccount;
secrets: ISecret[];
requiredPermissions?: string[];
}) => {
const permissions = await ServiceAccountWorkspacePermission.find({
serviceAccount: serviceAccount._id,
});
const permissionsObj = _.keyBy(permissions, (p) => {
return `${p.workspace.toString()}-${p.environment}`
});
secrets.forEach((secret: ISecret) => {
const permission = permissionsObj[`${secret.workspace.toString()}-${secret.environment}`];
if (!permission) throw BadRequestError({
message: "Failed to find any permission for the secret workspace and environment",
});
requiredPermissions?.forEach((requiredPermission: string) => {
let runningIsDisallowed = false;
requiredPermissions?.forEach((requiredPermission: string) => {
switch (requiredPermission) {
case PERMISSION_READ_SECRETS:
if (!permission.read) runningIsDisallowed = true;
break;
case PERMISSION_WRITE_SECRETS:
if (!permission.write) runningIsDisallowed = true;
break;
default:
break;
}
if (runningIsDisallowed) {
throw UnauthorizedRequestError({
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`,
});
}
});
});
});
}
/**
* Validate that service account (client) can access target service
* account [serviceAccount] with required permissions [requiredPermissions]
* @param {Object} obj
* @param {SerivceAccount} obj.serviceAccount - service account client
* @param {ServiceAccount} targetServiceAccount - target service account to validate against
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
*/
export const validateServiceAccountClientForServiceAccount = ({
serviceAccount,
targetServiceAccount,
requiredPermissions,
}: {
serviceAccount: IServiceAccount;
targetServiceAccount: IServiceAccount;
requiredPermissions?: string[];
}) => {
if (!serviceAccount.organization.equals(targetServiceAccount.organization)) {
throw UnauthorizedRequestError({
message: "Failed service account authorization for the given service account",
});
}
}
/**
* Validate that service account (client) can access organization [organization]
* @param {Object} obj
* @param {User} obj.user - service account client
* @param {Organization} obj.organization - organization to validate against
*/
export const validateServiceAccountClientForOrganization = async ({
serviceAccount,
organization,
}: {
serviceAccount: IServiceAccount;
organization: IOrganization;
}) => {
if (!serviceAccount.organization.equals(organization._id)) {
throw UnauthorizedRequestError({
message: "Failed service account authorization for the given organization",
});
}
}

@ -1,7 +1,7 @@
import fs from "fs";
import path from "path";
import { Types } from "mongoose";
import { IOrganization, ISecret, IServiceAccount, IUser, Membership } from "../models";
import { IOrganization, ISecret, IUser, Membership } from "../models";
import { validateMembership } from "../helpers/membership";
import _ from "lodash";
import { BadRequestError, UnauthorizedRequestError, ValidationError } from "../utils/errors";
@ -172,35 +172,6 @@ export const validateUserClientForSecrets = async ({
});
};
/**
* Validate that user (client) can access service account [serviceAccount]
* with required permissions [requiredPermissions]
* @param {Object} obj
* @param {User} obj.user - user client
* @param {ServiceAccount} obj.serviceAccount - service account to validate against
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
*/
export const validateUserClientForServiceAccount = async ({
user,
serviceAccount,
requiredPermissions
}: {
user: IUser;
serviceAccount: IServiceAccount;
requiredPermissions?: string[];
}) => {
if (!serviceAccount.user.equals(user._id)) {
// case: user who created service account is not the
// same user that is on the request
await validateMembershipOrg({
userId: user._id,
organizationId: serviceAccount.organization,
acceptedRoles: [],
acceptedStatuses: []
});
}
};
/**
* Validate that user (client) can access organization [organization]
* @param {Object} obj

@ -1,94 +0,0 @@
import React, { Fragment } from "react";
import { useTranslation } from "react-i18next";
import {
faAngleDown,
faEye,
faPlus,
faShuffle,
faTrash,
faX
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Listbox, Transition } from "@headlessui/react";
interface ListBoxProps {
selected: string;
select: (event: string) => void;
}
const eventOptions = [
{
name: "addSecrets",
icon: faPlus
},
{
name: "readSecrets",
icon: faEye
},
{
name: "updateSecrets",
icon: faShuffle
},
{
name: "deleteSecrets",
icon: faTrash
}
];
/**
* This is the component that we use for the event picker in the activity logs tab.
* @param {object} obj
* @param {string} obj.selected - the event that is currently selected
* @param {function} obj.select - an action that happens when an item is selected
*/
const EventFilter = ({ selected, select }: ListBoxProps): JSX.Element => {
const { t } = useTranslation();
return (
<Listbox value={t(`activity.event.${selected}`)} onChange={select}>
<div className="relative">
<Listbox.Button className="flex h-10 w-52 cursor-pointer items-center justify-between rounded-md bg-mineshaft-800 pl-4 pr-2 text-sm text-bunker-200 duration-200 hover:bg-mineshaft-700">
{selected !== "" ? (
<p className="select-none text-bunker-100">{t(`activity.event.${selected}`)}</p>
) : (
<p className="select-none">{String(t("common.select-event"))}</p>
)}
{selected !== "" ? (
<FontAwesomeIcon icon={faX} className="w-2 p-2 pl-2" onClick={() => select("")} />
) : (
<FontAwesomeIcon icon={faAngleDown} className="pl-4 pr-2" />
)}
</Listbox.Button>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute z-50 mt-1 max-h-60 w-52 overflow-auto rounded-md border border-mineshaft-700 bg-bunker p-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{eventOptions.map((event, id) => (
<Listbox.Option
key={`${event.name}.${id + 1}`}
className={`flex h-10 cursor-pointer items-center rounded-md px-4 text-sm text-bunker-200 hover:bg-mineshaft-700 ${
selected === t(`activity.event.${event.name}`) && "bg-mineshaft-700"
}`}
value={event.name}
>
{({ selected: isSelected }) => (
<span
className={`block truncate ${isSelected ? "font-semibold" : "font-normal"}`}
>
<FontAwesomeIcon icon={event.icon} className="pr-4" />{" "}
{t(`activity.event.${event.name}`)}
</span>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
);
};
export default EventFilter;

@ -1,76 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface WorkspaceProps {
workspaceId: string;
offset: number;
limit: number;
userId: string;
actionNames: string;
}
/**
* This function fetches the activity logs for a certain project
* @param {object} obj
* @param {string} obj.workspaceId - workspace id for which we are trying to get project log
* @param {object} obj.offset - teh starting point of logs that we want to pull
* @param {object} obj.limit - how many logs will we output
* @param {object} obj.userId - optional userId filter - will only query logs for that user
* @param {string} obj.actionNames - optional actionNames filter - will only query logs for those actions
* @returns
*/
const getProjectLogs = async ({
workspaceId,
offset,
limit,
userId,
actionNames
}: WorkspaceProps) => {
let payload;
if (userId !== "" && actionNames !== "") {
payload = {
offset: String(offset),
limit: String(limit),
sortBy: "recent",
userId: JSON.stringify(userId),
actionNames
};
} else if (userId !== "") {
payload = {
offset: String(offset),
limit: String(limit),
sortBy: "recent",
userId: JSON.stringify(userId)
};
} else if (actionNames !== "") {
payload = {
offset: String(offset),
limit: String(limit),
sortBy: "recent",
actionNames
};
} else {
payload = {
offset: String(offset),
limit: String(limit),
sortBy: "recent"
};
}
return SecurityClient.fetchCall(
`/api/v1/workspace/${workspaceId}/logs?${new URLSearchParams(payload)}`,
{
method: "GET",
headers: {
"Content-Type": "application/json"
}
}
).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).logs;
}
console.log("Failed to get project logs");
return undefined;
});
};
export default getProjectLogs;

@ -1,237 +0,0 @@
// TODO: deprecate in favor of new audit logs
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Image from "next/image";
import { useRouter } from "next/router";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import getActionData from "@app/ee/api/secrets/GetActionData";
import patienceDiff from "@app/ee/utilities/findTextDifferences";
import {
useGetUserWsKey
} from "@app/hooks/api";
import {
decryptAssymmetric,
decryptSymmetric
} from "../../components/utilities/cryptography/crypto";
interface SideBarProps {
toggleSidebar: (value: string) => void;
currentAction: string;
}
interface SecretProps {
secret: string;
secretKeyCiphertext: string;
secretKeyHash: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueHash: string;
secretValueIV: string;
secretValueTag: string;
}
interface DecryptedSecretProps {
newSecretVersion: {
key: string;
value: string;
};
oldSecretVersion: {
key: string;
value: string;
};
}
interface ActionProps {
name: string;
}
/**
* @param {object} obj
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
* @param {string} obj.currentAction - the action id for which a sidebar is being displayed
* @returns the sidebar with the payload of user activity logs
*/
const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
const { t } = useTranslation();
const router = useRouter();
const [actionData, setActionData] = useState<DecryptedSecretProps[]>();
const [actionMetaData, setActionMetaData] = useState<ActionProps>();
const [isLoading, setIsLoading] = useState(false);
const { data: wsKey } = useGetUserWsKey(String(router.query.id));
useEffect(() => {
const getLogData = async () => {
setIsLoading(true);
const tempActionData = await getActionData({ actionId: currentAction });
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
// #TODO: make this a separate function and reuse across the app
let decryptedLatestKey: string;
if (wsKey) {
// assymmetrically decrypt symmetric key with local private key
decryptedLatestKey = decryptAssymmetric({
ciphertext: wsKey.encryptedKey,
nonce: wsKey.nonce,
publicKey: wsKey.sender.publicKey,
privateKey: String(PRIVATE_KEY)
});
const decryptedSecretVersions = tempActionData.payload.secretVersions.map(
(encryptedSecretVersion: {
newSecretVersion?: SecretProps;
oldSecretVersion?: SecretProps;
}) => ({
newSecretVersion: {
key: decryptSymmetric({
ciphertext: encryptedSecretVersion.newSecretVersion!.secretKeyCiphertext,
iv: encryptedSecretVersion.newSecretVersion!.secretKeyIV,
tag: encryptedSecretVersion.newSecretVersion!.secretKeyTag,
key: decryptedLatestKey
}),
value: decryptSymmetric({
ciphertext: encryptedSecretVersion.newSecretVersion!.secretValueCiphertext,
iv: encryptedSecretVersion.newSecretVersion!.secretValueIV,
tag: encryptedSecretVersion.newSecretVersion!.secretValueTag,
key: decryptedLatestKey
})
},
oldSecretVersion: {
key: encryptedSecretVersion.oldSecretVersion?.secretKeyCiphertext
? decryptSymmetric({
ciphertext: encryptedSecretVersion.oldSecretVersion?.secretKeyCiphertext,
iv: encryptedSecretVersion.oldSecretVersion?.secretKeyIV,
tag: encryptedSecretVersion.oldSecretVersion?.secretKeyTag,
key: decryptedLatestKey
})
: undefined,
value: encryptedSecretVersion.oldSecretVersion?.secretValueCiphertext
? decryptSymmetric({
ciphertext: encryptedSecretVersion.oldSecretVersion?.secretValueCiphertext,
iv: encryptedSecretVersion.oldSecretVersion?.secretValueIV,
tag: encryptedSecretVersion.oldSecretVersion?.secretValueTag,
key: decryptedLatestKey
})
: undefined
}
})
);
setActionData(decryptedSecretVersions);
setActionMetaData({ name: tempActionData.name });
setIsLoading(false);
}
};
getLogData();
}, [currentAction, wsKey]);
return (
<div
className={`absolute border-l border-mineshaft-500 ${
isLoading ? "bg-bunker-800" : "bg-bunker"
} fixed right-0 z-40 flex h-[calc(100vh)] w-96 flex-col justify-between shadow-xl`}
>
{isLoading ? (
<div className="mb-8 flex h-full items-center justify-center">
<Image
src="/images/loading/loading.gif"
height={60}
width={100}
alt="infisical loading indicator"
/>
</div>
) : (
<div className="h-min">
<div className="flex flex-row items-center justify-between border-b border-mineshaft-500 px-4 py-3">
<p className="text-lg font-semibold text-bunker-200">
{t(`activity.event.${actionMetaData?.name}`)}
</p>
<div
className="p-1"
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={() => toggleSidebar("")}
>
<FontAwesomeIcon icon={faXmark} className="h-4 w-4 cursor-pointer text-bunker-300" />
</div>
</div>
<div className="overflow-y-autp flex h-[calc(100vh-120px)] flex-col overflow-y-auto px-4">
{(actionMetaData?.name === "readSecrets" ||
actionMetaData?.name === "addSecrets" ||
actionMetaData?.name === "deleteSecrets") &&
actionData?.map((item, id) => (
<div key={`secret.${id + 1}`}>
<div className="ph-no-capture mt-4 pl-1 text-xs text-bunker-200">
{item.newSecretVersion.key}
</div>
<div className="w-full break-all rounded-md border border-mineshaft-500 bg-mineshaft-600 px-2 py-0.5 font-mono text-sm text-bunker-200">
{item.newSecretVersion.value ? (
<span> {item.newSecretVersion.value} </span>
) : (
<span className="text-bunker-400"> EMPTY </span>
)}
</div>
</div>
))}
{actionMetaData?.name === "updateSecrets" &&
actionData?.map((item, id) => (
<>
<div className="mt-4 pl-1 text-xs text-bunker-200">
{item.newSecretVersion.key}
</div>
<div className="overflow-hidden break-all rounded-md border border-mineshaft-500 font-mono text-bunker-200">
<div className="ph-no-capture bg-red/40 px-2">
-{" "}
{patienceDiff(
item.oldSecretVersion.value.split(""),
item.newSecretVersion.value.split(""),
false
).lines.map(
(character, lineId) =>
character.bIndex !== -1 && (
<span
key={`actionData.${id + 1}.line.${lineId + 1}`}
className={`${
character.aIndex === -1 && "bg-red-700/80 text-bunker-100"
}`}
>
{character.line}
</span>
)
)}
</div>
<div className="ph-no-capture break-all bg-green-500/40 px-2">
+{" "}
{patienceDiff(
item.oldSecretVersion.value.split(""),
item.newSecretVersion.value.split(""),
false
).lines.map(
(character, lineId) =>
character.aIndex !== -1 && (
<span
key={`actionData.${id + 1}.linev2.${lineId + 1}`}
className={`${
character.bIndex === -1 && "bg-green-700/80 text-bunker-100"
}`}
>
{character.line}
</span>
)
)}
</div>
</div>
</>
))}
</div>
</div>
)}
</div>
);
};
export default ActivitySideBar;

@ -1,205 +0,0 @@
// TODO: deprecate in favor of new audit logs
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { faAngleDown, faAngleRight, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import timeSince from "@app/ee/utilities/timeSince";
import guidGenerator from "../../components/utilities/randomId";
interface PayloadProps {
_id: string;
name: string;
secretVersions: string[];
}
interface LogData {
_id: string;
channel: string;
createdAt: string;
ipAddress: string;
user: string;
serviceAccount: {
name: string;
};
serviceTokenData: {
name: string;
};
payload: PayloadProps[];
}
/**
* This is a single row of the activity table
* @param obj
* @param {LogData} obj.row - data for a certain event
* @param {function} obj.toggleSidebar - open and close sidebar that displays data for a specific event
* @returns
*/
const ActivityLogsRow = ({
row,
toggleSidebar
}: {
row: LogData;
toggleSidebar: (value: string) => void;
}) => {
const [payloadOpened, setPayloadOpened] = useState(false);
const { t } = useTranslation();
const renderUser = () => {
if (row?.user) return `${row.user}`;
if (row?.serviceAccount) return `Service Account: ${row.serviceAccount.name}`;
if (row?.serviceTokenData?.name) return `Service Token: ${row.serviceTokenData.name}`;
return "";
};
return (
<>
<div key={guidGenerator()} className="w-full bg-mineshaft-800 text-sm text-mineshaft-200 duration-100 flex flex-row items-center">
<button
type="button"
onClick={() => setPayloadOpened(!payloadOpened)}
className="border-t border-mineshaft-700 pt-[0.58rem]"
>
<FontAwesomeIcon
icon={payloadOpened ? faAngleDown : faAngleRight}
className={`ml-6 mb-2 text-mineshaft-300 cursor-pointer ${
payloadOpened ? "bg-mineshaft-500 hover:bg-mineshaft-500" : "hover:bg-mineshaft-700"
} h-4 w-4 rounded-md p-1 duration-100`}
/>
</button>
<div className="border-t border-mineshaft-700 py-3 w-1/4 pl-6">
{row.payload
?.map(
(action) =>
`${String(action.secretVersions.length)} ${t(`activity.event.${action.name}`)}`
)
.join(" and ")}
</div>
<div className="border-t border-mineshaft-700 py-3 pl-6 w-1/4">{renderUser()}</div>
<div className="border-t border-mineshaft-700 py-3 pl-6 w-1/4">{row.channel}</div>
<div className="border-t border-mineshaft-700 py-3 pl-6 w-1/4">
{timeSince(new Date(row.createdAt))}
</div>
</div>
{payloadOpened && (
<div className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200 bg-mineshaft-900/50 w-full flex flex-row items-center">
<div className='max-w-xl w-full flex flex-row items-center'>
<div className='w-24' />
<div className='w-1/2'>{String(t("common.timestamp"))}</div>
<div className='w-1/2'>{row.createdAt}</div>
</div>
</div>
)}
{payloadOpened &&
row.payload?.map(
(action) =>
action.secretVersions.length > 0 && (
<div
key={action.name}
className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200 bg-mineshaft-900/50 w-full flex flex-row items-center"
>
<div className='max-w-xl w-full flex flex-row items-center'>
<div className='w-24' />
<div className='w-1/2'>{t(`activity.event.${action.name}`)}</div>
<button
type="button"
onClick={() => toggleSidebar(action._id)}
className='w-1/2 text-primary-300 hover:text-primary-500 flex flex-row justify-left items-center duration-100'
>
{action.secretVersions.length +
(action.secretVersions.length !== 1 ? " secrets" : " secret")}
<FontAwesomeIcon
icon={faUpRightFromSquare}
className="ml-2 mb-0.5 h-3 w-3 font-light"
/>
</button>
</div>
</div>
)
)}
{payloadOpened && (
<div className="h-9 border-t border-mineshaft-700 text-sm text-bunker-200 bg-mineshaft-900/50 w-full flex flex-row items-center">
<div className='max-w-xl w-full flex flex-row items-center'>
<div className='w-24' />
<div className='w-1/2'>{String(t("activity.ip-address"))}</div>
<div className='w-1/2'>{row.ipAddress}</div>
</div>
</div>
)}
</>
);
};
/**
* This is the table for activity logs (one of the tabs)
* @param {object} obj
* @param {logData} obj.data - data for user activity logs
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
* @param {boolean} obj.isLoading - whether the log data has been loaded yet or not
* @returns
*/
const ActivityTable = ({
data,
toggleSidebar,
isLoading
}: {
data: LogData[];
toggleSidebar: (value: string) => void;
isLoading: boolean;
}) => {
const { t } = useTranslation();
return (
<div className="mt-8 w-full px-6">
<div className="table-container relative mb-6 w-full rounded-md border border-mineshaft-700 bg-mineshaft-800">
{/* <div className="absolute h-[3rem] w-full rounded-t-md bg-white/5" /> */}
<div className="my-1 w-full">
<div className="text-bunker-300 border-b border-mineshaft-600">
<div className="text-sm flex flex-row w-full">
<button
type="button"
onClick={() => {}}
className="opacity-0"
>
<FontAwesomeIcon
icon={faAngleRight}
className="ml-6 mb-2 text-bunker-100 hover:bg-mineshaft-700 cursor-pointer h-4 w-4 rounded-md p-1 duration-100"
/>
</button>
<div className="flex flex-row justify-between w-full">
<div className="pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
{String(t("common.event")).toUpperCase()}
</div>
<div className="pl-6 pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
{String(t("common.user")).toUpperCase()}
</div>
<div className="pl-6 pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
{String(t("common.source")).toUpperCase()}
</div>
<div className="pl-6 pt-2.5 pb-3 text-left font-semibold w-1/4 pl-6">
{String(t("common.time")).toUpperCase()}
</div>
</div>
</div>
</div>
{data?.map((row, index) => (
<ActivityLogsRow
key={`activity.${index + 1}.${row._id}`}
row={row}
toggleSidebar={toggleSidebar}
/>
))}
</div>
</div>
{isLoading && (
<div className="mb-8 mt-4 bg-mineshaft-800 rounded-md h-60 flex w-full justify-center animate-pulse" />
)}
</div>
);
};
export default ActivityTable;

@ -15,7 +15,6 @@ export * from "./secretImports";
export * from "./secretRotation";
export * from "./secrets";
export * from "./secretSnapshots";
export * from "./serviceAccounts";
export * from "./serviceTokens";
export * from "./ssoConfig";
export * from "./subscriptions";

@ -1,10 +0,0 @@
export {
useCreateServiceAccount,
useCreateServiceAccountProjectLevelPermission,
useDeleteServiceAccount,
useDeleteServiceAccountProjectLevelPermission,
useGetServiceAccountById,
useGetServiceAccountProjectLevelPermissions,
useGetServiceAccounts,
useRenameServiceAccount
} from "./queries";

@ -1,137 +0,0 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import {
CreateServiceAccountDTO,
CreateServiceAccountRes,
CreateServiceAccountWorkspacePermissionDTO,
DeleteServiceAccountWorkspacePermissionDTO,
RenameServiceAccountDTO,
ServiceAccount,
ServiceAccountWorkspacePermission
} from "./types";
const serviceAccountKeys = {
getServiceAccountById: (serviceAccountId: string) => [{ serviceAccountId }, "service-account"] as const,
getServiceAccounts: (organizationID: string) => [{ organizationID }, "service-accounts"] as const,
getServiceAccountProjectLevelPermissions: (serviceAccountId: string) => [{ serviceAccountId }, "service-account-project-level-permissions"] as const
}
const fetchServiceAccounts = async (organizationID: string) => {
const { data } = await apiRequest.get<{ serviceAccounts: ServiceAccount[] }>(
`/api/v2/organizations/${organizationID}/service-accounts`
);
return data.serviceAccounts;
}
const fetchServiceAccountById = async (serviceAccountId: string) => {
const { data } = await apiRequest.get<{ serviceAccount: ServiceAccount }>(
`/api/v2/service-accounts/${serviceAccountId}`
);
return data.serviceAccount;
}
export const useGetServiceAccounts = (organizationID: string) =>
useQuery({
queryKey: serviceAccountKeys.getServiceAccounts(organizationID),
queryFn: () => fetchServiceAccounts(organizationID),
enabled: Boolean(organizationID)
});
export const useGetServiceAccountById = (serviceAccountId: string) => {
return useQuery({
queryKey: serviceAccountKeys.getServiceAccountById(serviceAccountId),
queryFn: () => fetchServiceAccountById(serviceAccountId),
enabled: true
});
}
export const useCreateServiceAccount = () => {
const queryClient = useQueryClient();
return useMutation<CreateServiceAccountRes, {}, CreateServiceAccountDTO>({
mutationFn: async (body) => {
const { data } = await apiRequest.post("/api/v2/service-accounts/", body);
return data;
},
onSuccess: ({ serviceAccount }) => {
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccounts(serviceAccount.organization));
}
});
}
export const useRenameServiceAccount = () => {
const queryClient = useQueryClient();
return useMutation<ServiceAccount, {}, RenameServiceAccountDTO>({
mutationFn: async ({ serviceAccountId, name }) => {
const { data: { serviceAccount } } = await apiRequest.patch(`/api/v2/service-accounts/${serviceAccountId}/name`, { name });
return serviceAccount;
},
onSuccess: (serviceAccount) => {
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccountById(serviceAccount._id));
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccounts(serviceAccount.organization));
}
});
}
export const useDeleteServiceAccount = () => {
const queryClient = useQueryClient();
return useMutation<ServiceAccount, {}, string>({
mutationFn: async (serviceAccountId) => {
const { data: { serviceAccount } } = await apiRequest.delete(`/api/v2/service-accounts/${serviceAccountId}`);
return serviceAccount;
},
onSuccess: ({ organization }) => {
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccounts(organization));
}
});
}
const fetchServiceAccountProjectLevelPermissions = async (serviceAccountId: string) => {
const { data: { serviceAccountWorkspacePermissions } } = await apiRequest.get<{ serviceAccountWorkspacePermissions: ServiceAccountWorkspacePermission[] }>(
`/api/v2/service-accounts/${serviceAccountId}/permissions/workspace`
);
return serviceAccountWorkspacePermissions;
}
export const useGetServiceAccountProjectLevelPermissions = (serviceAccountId: string) => {
return useQuery({
queryKey: serviceAccountKeys.getServiceAccountProjectLevelPermissions(serviceAccountId),
queryFn: () => fetchServiceAccountProjectLevelPermissions(serviceAccountId),
enabled: true
});
}
export const useCreateServiceAccountProjectLevelPermission = () => {
const queryClient = useQueryClient();
return useMutation<ServiceAccountWorkspacePermission, {}, CreateServiceAccountWorkspacePermissionDTO>({
mutationFn: async (body) => {
const { data: { serviceAccountWorkspacePermission } } = await apiRequest.post(`/api/v2/service-accounts/${body.serviceAccountId}/permissions/workspace`, body);
return serviceAccountWorkspacePermission;
},
onSuccess: ({ serviceAccount }) => {
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccountProjectLevelPermissions(serviceAccount));
}
});
}
export const useDeleteServiceAccountProjectLevelPermission = () => {
const queryClient = useQueryClient();
return useMutation<ServiceAccountWorkspacePermission, {}, DeleteServiceAccountWorkspacePermissionDTO>({
mutationFn: async ({ serviceAccountId, serviceAccountWorkspacePermissionId }) => {
const { data: { serviceAccountWorkspacePermission} } = await apiRequest.delete(`/api/v2/service-accounts/${serviceAccountId}/permissions/workspace/${serviceAccountWorkspacePermissionId}`);
return serviceAccountWorkspacePermission;
},
onSuccess: (serviceAccountWorkspacePermission) => {
queryClient.invalidateQueries(serviceAccountKeys.getServiceAccountProjectLevelPermissions(serviceAccountWorkspacePermission.serviceAccount));
}
});
}

@ -1,51 +0,0 @@
import { Workspace } from "../workspace/types";
export type ServiceAccount = {
_id: string;
name: string;
organization: string;
user: string;
publicKey: string;
expiresAt: string;
}
export type CreateServiceAccountDTO = {
name: string;
organizationId: string;
publicKey: string;
expiresIn: number;
}
export type CreateServiceAccountRes = {
serviceAccount: ServiceAccount;
serviceAccountAccessKey: string;
}
export type RenameServiceAccountDTO = {
serviceAccountId: string;
name: string;
}
export type ServiceAccountWorkspacePermission = {
_id: string;
serviceAccount: string;
workspace: Workspace;
environment: string;
read: boolean;
write: boolean;
}
export type CreateServiceAccountWorkspacePermissionDTO = {
serviceAccountId: string;
workspaceId: string;
environment: string;
read: boolean;
write: boolean;
encryptedKey: string;
nonce: string;
}
export type DeleteServiceAccountWorkspacePermissionDTO = {
serviceAccountId: string;
serviceAccountWorkspacePermissionId: string;
}

@ -1,204 +0,0 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Head from "next/head";
import { useRouter } from "next/router";
import Button from "@app/components/basic/buttons/Button";
import EventFilter from "@app/components/basic/EventFilter";
import { UpgradePlanModal } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useSubscription } from "@app/context";
import ActivitySideBar from "@app/ee/components/ActivitySideBar";
import { withProjectPermission } from "@app/hoc";
import { usePopUp } from "@app/hooks/usePopUp";
import getProjectLogs from "../../../../ee/api/secrets/GetProjectLogs";
import ActivityTable from "../../../../ee/components/ActivityTable";
interface LogData {
_id: string;
channel: string;
createdAt: string;
ipAddress: string;
user: {
email: string;
};
serviceAccount?: {
string: string;
};
serviceTokenData?: {
name: string;
};
actions: {
_id: string;
name: string;
payload: {
secretVersions: string[];
};
}[];
}
interface PayloadProps {
_id: string;
name: string;
secretVersions: string[];
}
interface LogDataPoint {
_id: string;
channel: string;
createdAt: string;
ipAddress: string;
user: string;
serviceAccount: {
name: string;
};
serviceTokenData: {
name: string;
};
payload: PayloadProps[];
}
/**
* This is the tab that includes all of the user activity logs
*/
const Activity = withProjectPermission(
() => {
const router = useRouter();
const [eventChosen, setEventChosen] = useState("");
const [logsData, setLogsData] = useState<LogDataPoint[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [currentOffset, setCurrentOffset] = useState(0);
const currentLimit = 10;
const [currentSidebarAction, toggleSidebar] = useState<string>();
const { t } = useTranslation();
const { subscription } = useSubscription();
const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["upgradePlan"] as const);
// this use effect updates the data in case of a new filter being added
useEffect(() => {
setCurrentOffset(0);
const getLogData = async () => {
setIsLoading(true);
const tempLogsData = await getProjectLogs({
workspaceId: String(router.query.id),
offset: 0,
limit: currentLimit,
userId: "",
actionNames: eventChosen
});
setLogsData(
tempLogsData.map((log: LogData) => ({
_id: log._id,
channel: log.channel,
createdAt: log.createdAt,
ipAddress: log.ipAddress,
user: log?.user?.email,
serviceAccount: log?.serviceAccount,
serviceTokenData: log?.serviceTokenData,
payload: log.actions.map((action) => ({
_id: action._id,
name: action.name,
secretVersions: action.payload.secretVersions
}))
}))
);
setIsLoading(false);
};
getLogData();
}, [eventChosen]);
// this use effect adds more data in case 'View More' button is clicked
useEffect(() => {
const getLogData = async () => {
setIsLoading(true);
const tempLogsData = await getProjectLogs({
workspaceId: String(router.query.id),
offset: currentOffset,
limit: currentLimit,
userId: "",
actionNames: eventChosen
});
setLogsData(
logsData.concat(
tempLogsData.map((log: LogData) => ({
_id: log._id,
channel: log.channel,
createdAt: log.createdAt,
ipAddress: log.ipAddress,
user: log?.user?.email,
serviceAccount: log?.serviceAccount,
serviceTokenData: log?.serviceTokenData,
payload: log.actions.map((action) => ({
_id: action._id,
name: action.name,
secretVersions: action.payload.secretVersions
}))
}))
)
);
setIsLoading(false);
};
getLogData();
}, [currentLimit, currentOffset]);
const loadMoreLogs = () => {
if (subscription?.auditLogs === false) {
handlePopUpOpen("upgradePlan");
} else {
setCurrentOffset(currentOffset + currentLimit);
}
};
return (
<div className="mx-auto w-full h-full max-w-7xl">
<Head>
<title>Audit Logs</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
</Head>
{currentSidebarAction && (
<ActivitySideBar toggleSidebar={toggleSidebar} currentAction={currentSidebarAction} />
)}
<div className="flex flex-col justify-between items-start mx-4 mb-4 text-xl px-2">
<div className="flex flex-row justify-start items-center text-3xl mt-6">
<p className="font-semibold mr-4 text-bunker-100">{t("activity.title")}</p>
</div>
<p className="mr-4 text-base text-gray-400">{t("activity.subtitle")}</p>
</div>
<div className="px-6 h-8 mt-2">
<EventFilter selected={eventChosen} select={setEventChosen} />
</div>
<ActivityTable data={logsData} toggleSidebar={toggleSidebar} isLoading={isLoading} />
<div className="flex justify-center w-full mb-6">
<div className="items-center w-60">
<Button
text={String(t("common.view-more"))}
textDisabled={String(t("common.end-of-history"))}
active={logsData.length % 10 === 0}
onButtonPressed={loadMoreLogs}
size="md"
color="mineshaft"
/>
</div>
</div>
{subscription && (
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={() => handlePopUpClose("upgradePlan")}
text={
subscription.slug === null
? "You can see more logs under an Enterprise license"
: "You can see more logs if you switch to Infisical's Business/Professional Plan."
}
/>
)}
</div>
);
},
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.AuditLogs }
);
Object.assign(Activity, { requireAuth: true });
export default Activity;

@ -1,389 +0,0 @@
import { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/router";
import {
faCheck,
faCopy,
faMagnifyingGlass,
faPencil,
faPlus,
faServer,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// import { yupResolver } from "@hookform/resolvers/yup";
// import * as yup from "yup";
// import { generateKeyPair } from "@app/components/utilities/cryptography/crypto";
import {
Button,
DeleteActionModal,
EmptyState,
// FormControl,
IconButton,
Input,
Modal,
ModalContent,
// Select,
// SelectItem,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import {
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization,
useWorkspace
} from "@app/context";
import { withPermission } from "@app/hoc";
import { usePopUp, useToggle } from "@app/hooks";
import {
// useCreateServiceAccount,
useDeleteServiceAccount,
useGetServiceAccounts
} from "@app/hooks/api";
import // Controller,
// useForm
"react-hook-form";
// const serviceAccountExpiration = [
// { label: "1 Day", value: 86400 },
// { label: "7 Days", value: 604800 },
// { label: "1 Month", value: 2592000 },
// { label: "6 months", value: 15552000 },
// { label: "12 months", value: 31104000 },
// { label: "Never", value: -1 }
// ];
// const addServiceAccountFormSchema = yup.object({
// name: yup.string().required().label("Name").trim(),
// expiresIn: yup.string().required().label("Service Account Expiration")
// });
// type TAddServiceAccountForm = yup.InferType<typeof addServiceAccountFormSchema>;
export const OrgServiceAccountsTable = withPermission(
() => {
const router = useRouter();
const { currentOrg } = useOrganization();
const { currentWorkspace } = useWorkspace();
const orgId = currentOrg?._id || "";
const [step, setStep] = useState(0);
const [isAccessKeyCopied, setIsAccessKeyCopied] = useToggle(false);
const [isPublicKeyCopied, setIsPublicKeyCopied] = useToggle(false);
const [isPrivateKeyCopied, setIsPrivateKeyCopied] = useToggle(false);
const [accessKey] = useState("");
const [publicKey] = useState("");
const [privateKey] = useState("");
const [searchServiceAccountFilter, setSearchServiceAccountFilter] = useState("");
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"addServiceAccount",
"removeServiceAccount"
] as const);
const { data: serviceAccounts = [], isLoading: isServiceAccountsLoading } =
useGetServiceAccounts(orgId);
// const createServiceAccount = useCreateServiceAccount();
const removeServiceAccount = useDeleteServiceAccount();
useEffect(() => {
let timer: NodeJS.Timeout;
if (isAccessKeyCopied) {
timer = setTimeout(() => setIsAccessKeyCopied.off(), 2000);
}
if (isPublicKeyCopied) {
timer = setTimeout(() => setIsPublicKeyCopied.off(), 2000);
}
if (isPrivateKeyCopied) {
timer = setTimeout(() => setIsPrivateKeyCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [isAccessKeyCopied, isPublicKeyCopied, isPrivateKeyCopied]);
// const {
// control,
// handleSubmit,
// reset,
// formState: { isSubmitting }
// } = useForm<TAddServiceAccountForm>({ resolver: yupResolver(addServiceAccountFormSchema) });
// const onAddServiceAccount = async ({ name, expiresIn }: TAddServiceAccountForm) => {
// if (!currentOrg?._id) return;
// const keyPair = generateKeyPair();
// setPublicKey(keyPair.publicKey);
// setPrivateKey(keyPair.privateKey);
// const serviceAccountDetails = await createServiceAccount.mutateAsync({
// name,
// organizationId: currentOrg?._id,
// publicKey: keyPair.publicKey,
// expiresIn: Number(expiresIn)
// });
// setAccessKey(serviceAccountDetails.serviceAccountAccessKey);
// setStep(1);
// reset();
// }
const onRemoveServiceAccount = async () => {
const serviceAccountId = (popUp?.removeServiceAccount?.data as { _id: string })?._id;
await removeServiceAccount.mutateAsync(serviceAccountId);
handlePopUpClose("removeServiceAccount");
};
const filteredServiceAccounts = useMemo(
() =>
serviceAccounts.filter(({ name }) =>
name.toLowerCase().includes(searchServiceAccountFilter)
),
[serviceAccounts, searchServiceAccountFilter]
);
const renderStep = (stepToRender: number) => {
switch (stepToRender) {
case 0:
return (
<div>
We are currently revising the service account mechanism. In the meantime, please use
service tokens or API key to fetch secrets via API request.
</div>
// <form onSubmit={handleSubmit(onAddServiceAccount)}>
// <Controller
// control={control}
// defaultValue=""
// name="name"
// render={({ field, fieldState: { error } }) => (
// <FormControl label="Name" isError={Boolean(error)} errorText={error?.message}>
// <Input {...field} />
// </FormControl>
// )}
// />
// <Controller
// control={control}
// name="expiresIn"
// defaultValue={String(serviceAccountExpiration?.[0]?.value)}
// render={({ field: { onChange, ...field }, fieldState: { error } }) => {
// return (
// <FormControl
// label="Expiration"
// errorText={error?.message}
// isError={Boolean(error)}
// >
// <Select
// defaultValue={field.value}
// {...field}
// onValueChange={(e) => onChange(e)}
// className="w-full"
// >
// {serviceAccountExpiration.map(({ label, value }) => (
// <SelectItem value={String(value)} key={label}>
// {label}
// </SelectItem>
// ))}
// </Select>
// </FormControl>
// );
// }}
// />
// <div className="mt-8 flex items-center">
// <Button
// className="mr-4"
// size="sm"
// type="submit"
// isLoading={isSubmitting}
// isDisabled={isSubmitting}
// >
// Create Service Account
// </Button>
// <Button
// colorSchema="secondary"
// variant="plain"
// onClick={() => handlePopUpClose("addServiceAccount")}
// >
// Cancel
// </Button>
// </div>
// </form>
);
case 1:
return (
<>
<p>Access Key</p>
<div className="flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{accessKey}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(accessKey);
setIsAccessKeyCopied.on();
}}
>
<FontAwesomeIcon icon={isAccessKeyCopied ? 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>
<p className="mt-4">Public Key</p>
<div className="flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{publicKey}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(publicKey);
setIsPublicKeyCopied.on();
}}
>
<FontAwesomeIcon icon={isPublicKeyCopied ? 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>
<p className="mt-4">Private Key</p>
<div className="flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{privateKey}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(privateKey);
setIsPrivateKeyCopied.on();
}}
>
<FontAwesomeIcon icon={isPrivateKeyCopied ? 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>
</>
);
default:
return <div />;
}
};
return (
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600 mb-6">
<div className="mb-4 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Service Accounts</p>
<Button
colorSchema="secondary"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
setStep(0);
// reset();
handlePopUpOpen("addServiceAccount");
}}
>
Add Service Account
</Button>
</div>
<Input
value={searchServiceAccountFilter}
onChange={(e) => setSearchServiceAccountFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search service accounts..."
/>
<TableContainer className="mt-4">
<Table>
<THead>
<Th>Name</Th>
<Th className="w-full">Valid Until</Th>
<Th aria-label="actions" />
</THead>
<TBody>
{isServiceAccountsLoading && (
<TableSkeleton columns={5} innerKey="org-service-accounts" />
)}
{!isServiceAccountsLoading &&
filteredServiceAccounts.map(({ name, expiresAt, _id: serviceAccountId }) => {
return (
<Tr key={`org-service-account-${serviceAccountId}`}>
<Td>{name}</Td>
<Td>{new Date(expiresAt).toUTCString()}</Td>
<Td>
<div className="flex">
<IconButton
ariaLabel="edit"
colorSchema="secondary"
onClick={() => {
if (currentWorkspace?._id) {
router.push(
`/settings/org/${currentWorkspace._id}/service-accounts/${serviceAccountId}`
);
}
}}
className="mr-2"
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
<IconButton
ariaLabel="delete"
colorSchema="danger"
onClick={() =>
handlePopUpOpen("removeServiceAccount", { _id: serviceAccountId })
}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</div>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{!isServiceAccountsLoading && filteredServiceAccounts?.length === 0 && (
<EmptyState title="No service accounts found" icon={faServer} />
)}
</TableContainer>
<Modal
isOpen={popUp?.addServiceAccount?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("addServiceAccount", isOpen);
// reset();
}}
>
<ModalContent
title="Add Service Account"
subTitle="A service account represents a machine identity such as a VM or application client."
>
{renderStep(step)}
</ModalContent>
</Modal>
<DeleteActionModal
isOpen={popUp.removeServiceAccount.isOpen}
deleteKey="remove"
title="Do you want to remove this service account from the org?"
onChange={(isOpen) => handlePopUpToggle("removeServiceAccount", isOpen)}
onDeleteApproved={onRemoveServiceAccount}
/>
</div>
);
},
{
action: OrgPermissionActions.Read,
subject: OrgPermissionSubjects.Settings,
containerClassName: "mb-4"
}
);

@ -1 +0,0 @@
export { OrgServiceAccountsTable } from "./OrgServiceAccountsTable";
Loading…
Cancel
Save