mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 18:27:36 +00:00
Remove/deprecate service accounts + old logs/actions
This commit is contained in:
@@ -6,14 +6,8 @@ const jsrp = require("jsrp");
|
|||||||
import { LoginSRPDetail, TokenVersion, User } from "../../models";
|
import { LoginSRPDetail, TokenVersion, User } from "../../models";
|
||||||
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
|
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
|
||||||
import { checkUserDevice } from "../../helpers/user";
|
import { checkUserDevice } from "../../helpers/user";
|
||||||
import {
|
import { AuthTokenType } from "../../variables";
|
||||||
ACTION_LOGIN,
|
|
||||||
ACTION_LOGOUT,
|
|
||||||
AuthTokenType
|
|
||||||
} from "../../variables";
|
|
||||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||||
import { EELogService } from "../../ee/services";
|
|
||||||
import { getUserAgentType } from "../../utils/posthog";
|
|
||||||
import {
|
import {
|
||||||
getAuthSecret,
|
getAuthSecret,
|
||||||
getHttpsEnabled,
|
getHttpsEnabled,
|
||||||
@@ -145,19 +139,6 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
secure: await getHttpsEnabled()
|
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 (access) token in response
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
token: tokens.token,
|
token: tokens.token,
|
||||||
@@ -194,19 +175,6 @@ export const logout = async (req: Request, res: Response) => {
|
|||||||
secure: (await getHttpsEnabled()) as boolean
|
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({
|
return res.status(200).send({
|
||||||
message: "Successfully logged out."
|
message: "Successfully logged out."
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ import { createToken, issueAuthTokens } from "../../helpers/auth";
|
|||||||
import { checkUserDevice } from "../../helpers/user";
|
import { checkUserDevice } from "../../helpers/user";
|
||||||
import { sendMail } from "../../helpers/nodemailer";
|
import { sendMail } from "../../helpers/nodemailer";
|
||||||
import { TokenService } from "../../services";
|
import { TokenService } from "../../services";
|
||||||
import { EELogService } from "../../ee/services";
|
|
||||||
import { BadRequestError, InternalServerError } from "../../utils/errors";
|
import { BadRequestError, InternalServerError } from "../../utils/errors";
|
||||||
import { ACTION_LOGIN, AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
|
import { AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
|
||||||
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
|
|
||||||
import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
|
import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
import * as reqValidator from "../../validation/auth";
|
import * as reqValidator from "../../validation/auth";
|
||||||
@@ -190,19 +188,6 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
response.protectedKeyTag = user.protectedKeyTag;
|
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);
|
return res.status(200).send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,18 +315,5 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
|
|||||||
resObj.protectedKeyTag = user.protectedKeyTag;
|
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);
|
return res.status(200).send(resObj);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
} from "../../ee/services/ProjectRoleService";
|
} from "../../ee/services/ProjectRoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { SecretImport } from "../../models";
|
import { SecretImport } from "../../models";
|
||||||
import { ServiceAccountWorkspacePermission } from "../../models";
|
|
||||||
import { Webhook } from "../../models";
|
import { Webhook } from "../../models";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -400,11 +399,6 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
|
|||||||
{ arrayFilters: [{ "element.environment": oldEnvironmentSlug }] },
|
{ arrayFilters: [{ "element.environment": oldEnvironmentSlug }] },
|
||||||
);
|
);
|
||||||
|
|
||||||
await ServiceAccountWorkspacePermission.updateMany(
|
|
||||||
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
|
||||||
{ environment: environmentSlug }
|
|
||||||
);
|
|
||||||
|
|
||||||
await Webhook.updateMany(
|
await Webhook.updateMany(
|
||||||
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
||||||
{ environment: environmentSlug }
|
{ environment: environmentSlug }
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import * as workspaceController from "./workspaceController";
|
|||||||
import * as serviceTokenDataController from "./serviceTokenDataController";
|
import * as serviceTokenDataController from "./serviceTokenDataController";
|
||||||
import * as secretController from "./secretController";
|
import * as secretController from "./secretController";
|
||||||
import * as secretsController from "./secretsController";
|
import * as secretsController from "./secretsController";
|
||||||
import * as serviceAccountsController from "./serviceAccountsController";
|
|
||||||
import * as environmentController from "./environmentController";
|
import * as environmentController from "./environmentController";
|
||||||
import * as tagController from "./tagController";
|
import * as tagController from "./tagController";
|
||||||
import * as membershipController from "./membershipController";
|
import * as membershipController from "./membershipController";
|
||||||
@@ -20,7 +19,6 @@ export {
|
|||||||
serviceTokenDataController,
|
serviceTokenDataController,
|
||||||
secretController,
|
secretController,
|
||||||
secretsController,
|
secretsController,
|
||||||
serviceAccountsController,
|
|
||||||
environmentController,
|
environmentController,
|
||||||
tagController,
|
tagController,
|
||||||
membershipController
|
membershipController
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Types } from "mongoose";
|
|||||||
import {
|
import {
|
||||||
Membership,
|
Membership,
|
||||||
MembershipOrg,
|
MembershipOrg,
|
||||||
ServiceAccount,
|
|
||||||
Workspace
|
Workspace
|
||||||
} from "../../models";
|
} from "../../models";
|
||||||
import { Role } from "../../ee/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]
|
* Create new organization named [organizationName]
|
||||||
* and add user as owner
|
* and add user as owner
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Folder, ISecret, Secret, ServiceTokenData, Tag } from "../../models";
|
import { Folder, ISecret, Secret, ServiceTokenData, Tag } from "../../models";
|
||||||
import { AuditLog, EventType, IAction, SecretVersion } from "../../ee/models";
|
import { AuditLog, EventType, SecretVersion } from "../../ee/models";
|
||||||
import {
|
import {
|
||||||
ACTION_ADD_SECRETS,
|
|
||||||
ACTION_DELETE_SECRETS,
|
|
||||||
ACTION_READ_SECRETS,
|
|
||||||
ACTION_UPDATE_SECRETS,
|
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
K8_USER_AGENT_NAME,
|
K8_USER_AGENT_NAME,
|
||||||
@@ -15,7 +11,7 @@ import {
|
|||||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||||
import { EventService } from "../../services";
|
import { EventService } from "../../services";
|
||||||
import { eventPushSecrets } from "../../events";
|
import { eventPushSecrets } from "../../events";
|
||||||
import { EEAuditLogService, EELogService, EESecretService } from "../../ee/services";
|
import { EEAuditLogService, EESecretService } from "../../ee/services";
|
||||||
import { SecretService, TelemetryService } from "../../services";
|
import { SecretService, TelemetryService } from "../../services";
|
||||||
import { getUserAgentType } from "../../utils/posthog";
|
import { getUserAgentType } from "../../utils/posthog";
|
||||||
import { PERMISSION_WRITE_SECRETS } from "../../variables";
|
import { PERMISSION_WRITE_SECRETS } from "../../variables";
|
||||||
@@ -82,7 +78,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
const createSecrets: any[] = [];
|
const createSecrets: any[] = [];
|
||||||
const updateSecrets: any[] = [];
|
const updateSecrets: any[] = [];
|
||||||
const deleteSecrets: { _id: Types.ObjectId; secretName: string }[] = [];
|
const deleteSecrets: { _id: Types.ObjectId; secretName: string }[] = [];
|
||||||
const actions: IAction[] = [];
|
|
||||||
|
|
||||||
// get secret blind index salt
|
// get secret blind index salt
|
||||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||||
@@ -224,16 +219,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
await AuditLog.insertMany(auditLogs);
|
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) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets added",
|
event: "secrets added",
|
||||||
@@ -350,14 +335,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
await AuditLog.insertMany(auditLogs);
|
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) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets modified",
|
event: "secrets modified",
|
||||||
@@ -428,14 +405,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
await AuditLog.insertMany(auditLogs);
|
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) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets deleted",
|
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
|
// // trigger event - push secrets
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
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
|
// (EE) take a secret snapshot
|
||||||
await EESecretService.takeSecretSnapshot({
|
await EESecretService.takeSecretSnapshot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
@@ -960,22 +897,6 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
secrets = await Secret.find(secretQuery).populate("tags");
|
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
|
// TODO(akhilmhdh) - secret-imp change this to org type
|
||||||
let importedSecrets: any[] = [];
|
let importedSecrets: any[] = [];
|
||||||
if (include_imports) {
|
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(
|
await EEAuditLogService.createAuditLog(
|
||||||
req.authData,
|
req.authData,
|
||||||
{
|
{
|
||||||
@@ -1247,27 +1146,6 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
// });
|
// });
|
||||||
// }, 10000);
|
// }, 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
|
// (EE) take a secret snapshot
|
||||||
// IMP(akhilmhdh): commented out due to unknown where the environment is
|
// IMP(akhilmhdh): commented out due to unknown where the environment is
|
||||||
// await EESecretService.takeSecretSnapshot({
|
// await EESecretService.takeSecretSnapshot({
|
||||||
@@ -1387,26 +1265,6 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
// workspaceId: new Types.ObjectId(key)
|
// 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
|
// (EE) take a secret snapshot
|
||||||
// IMP(akhilmhdh): Not sure how to take secretSnapshot
|
// 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 { checkUserDevice } from "../../helpers/user";
|
||||||
import { sendMail } from "../../helpers/nodemailer";
|
import { sendMail } from "../../helpers/nodemailer";
|
||||||
import { TokenService } from "../../services";
|
import { TokenService } from "../../services";
|
||||||
import { EELogService } from "../../ee/services";
|
|
||||||
import { BadRequestError, InternalServerError } from "../../utils/errors";
|
import { BadRequestError, InternalServerError } from "../../utils/errors";
|
||||||
import { ACTION_LOGIN, AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
|
import { AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
|
||||||
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
|
|
||||||
import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
|
import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
|
||||||
import { AuthMethod } from "../../models/user";
|
import { AuthMethod } from "../../models/user";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
@@ -215,19 +213,6 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
response.protectedKeyTag = user.protectedKeyTag;
|
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);
|
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 ssoController from "./ssoController";
|
||||||
import * as usersController from "./usersController";
|
import * as usersController from "./usersController";
|
||||||
import * as workspaceController from "./workspaceController";
|
import * as workspaceController from "./workspaceController";
|
||||||
import * as actionController from "./actionController";
|
|
||||||
import * as membershipController from "./membershipController";
|
import * as membershipController from "./membershipController";
|
||||||
import * as cloudProductsController from "./cloudProductsController";
|
import * as cloudProductsController from "./cloudProductsController";
|
||||||
import * as roleController from "./roleController";
|
import * as roleController from "./roleController";
|
||||||
@@ -20,7 +19,6 @@ export {
|
|||||||
ssoController,
|
ssoController,
|
||||||
usersController,
|
usersController,
|
||||||
workspaceController,
|
workspaceController,
|
||||||
actionController,
|
|
||||||
membershipController,
|
membershipController,
|
||||||
cloudProductsController,
|
cloudProductsController,
|
||||||
roleController,
|
roleController,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
FolderVersion,
|
FolderVersion,
|
||||||
IPType,
|
IPType,
|
||||||
ISecretVersion,
|
ISecretVersion,
|
||||||
Log,
|
|
||||||
SecretSnapshot,
|
SecretSnapshot,
|
||||||
SecretVersion,
|
SecretVersion,
|
||||||
ServiceActor,
|
ServiceActor,
|
||||||
@@ -38,7 +37,6 @@ import {
|
|||||||
DeleteWorkspaceTrustedIpV1,
|
DeleteWorkspaceTrustedIpV1,
|
||||||
GetWorkspaceAuditLogActorFilterOptsV1,
|
GetWorkspaceAuditLogActorFilterOptsV1,
|
||||||
GetWorkspaceAuditLogsV1,
|
GetWorkspaceAuditLogsV1,
|
||||||
GetWorkspaceLogsV1,
|
|
||||||
GetWorkspaceSecretSnapshotsCountV1,
|
GetWorkspaceSecretSnapshotsCountV1,
|
||||||
GetWorkspaceSecretSnapshotsV1,
|
GetWorkspaceSecretSnapshotsV1,
|
||||||
GetWorkspaceTrustedIpsV1,
|
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]
|
* Return audit logs for workspace with id [workspaceId]
|
||||||
* @param req
|
* @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 "./secretSnapshot";
|
||||||
export * from "./secretVersion";
|
export * from "./secretVersion";
|
||||||
export * from "./folderVersion";
|
export * from "./folderVersion";
|
||||||
export * from "./log";
|
|
||||||
export * from "./role";
|
export * from "./role";
|
||||||
export * from "./action";
|
|
||||||
export * from "./ssoConfig";
|
export * from "./ssoConfig";
|
||||||
export * from "./trustedIp";
|
export * from "./trustedIp";
|
||||||
export * from "./auditLog";
|
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 sso from "./sso";
|
||||||
import users from "./users";
|
import users from "./users";
|
||||||
import workspace from "./workspace";
|
import workspace from "./workspace";
|
||||||
import action from "./action";
|
|
||||||
import cloudProducts from "./cloudProducts";
|
import cloudProducts from "./cloudProducts";
|
||||||
import secretScanning from "./secretScanning";
|
import secretScanning from "./secretScanning";
|
||||||
import roles from "./role";
|
import roles from "./role";
|
||||||
@@ -20,7 +19,6 @@ export {
|
|||||||
sso,
|
sso,
|
||||||
users,
|
users,
|
||||||
workspace,
|
workspace,
|
||||||
action,
|
|
||||||
cloudProducts,
|
cloudProducts,
|
||||||
secretScanning,
|
secretScanning,
|
||||||
roles,
|
roles,
|
||||||
|
|||||||
@@ -28,14 +28,6 @@ router.post(
|
|||||||
workspaceController.rollbackWorkspaceSecretSnapshot
|
workspaceController.rollbackWorkspaceSecretSnapshot
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/:workspaceId/logs",
|
|
||||||
requireAuth({
|
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
|
||||||
}),
|
|
||||||
workspaceController.getWorkspaceLogs
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:workspaceId/audit-logs",
|
"/:workspaceId/audit-logs",
|
||||||
requireAuth({
|
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 EELicenseService from "./EELicenseService";
|
||||||
import EESecretService from "./EESecretService";
|
import EESecretService from "./EESecretService";
|
||||||
import EELogService from "./EELogService";
|
|
||||||
import EEAuditLogService from "./EEAuditLogService";
|
import EEAuditLogService from "./EEAuditLogService";
|
||||||
import GithubSecretScanningService from "./GithubSecretScanning/GithubSecretScanningService"
|
import GithubSecretScanningService from "./GithubSecretScanning/GithubSecretScanningService"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
EELicenseService,
|
EELicenseService,
|
||||||
EESecretService,
|
EESecretService,
|
||||||
EELogService,
|
|
||||||
EEAuditLogService,
|
EEAuditLogService,
|
||||||
GithubSecretScanningService
|
GithubSecretScanningService
|
||||||
}
|
}
|
||||||
@@ -23,13 +23,11 @@ import {
|
|||||||
Workspace
|
Workspace
|
||||||
} from "../models";
|
} from "../models";
|
||||||
import {
|
import {
|
||||||
Action,
|
|
||||||
AuditLog,
|
AuditLog,
|
||||||
FolderVersion,
|
FolderVersion,
|
||||||
GitAppInstallationSession,
|
GitAppInstallationSession,
|
||||||
GitAppOrganizationInstallation,
|
GitAppOrganizationInstallation,
|
||||||
GitRisks,
|
GitRisks,
|
||||||
Log,
|
|
||||||
Role,
|
Role,
|
||||||
SSOConfig,
|
SSOConfig,
|
||||||
SecretApprovalPolicy,
|
SecretApprovalPolicy,
|
||||||
@@ -288,18 +286,6 @@ export const deleteOrganization = async ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await Log.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Action.deleteMany({
|
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretApprovalPolicy.deleteMany({
|
await SecretApprovalPolicy.deleteMany({
|
||||||
workspace: {
|
workspace: {
|
||||||
$in: workspaceIds
|
$in: workspaceIds
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { ISecret, Secret } from "../models";
|
import { ISecret, Secret } from "../models";
|
||||||
import { EELogService, EESecretService } from "../ee/services";
|
import { EESecretService } from "../ee/services";
|
||||||
import { IAction, SecretVersion } from "../ee/models";
|
import { SecretVersion } from "../ee/models";
|
||||||
import {
|
import {
|
||||||
ACTION_ADD_SECRETS,
|
|
||||||
ACTION_DELETE_SECRETS,
|
|
||||||
ACTION_READ_SECRETS,
|
|
||||||
ACTION_UPDATE_SECRETS,
|
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
@@ -320,7 +316,6 @@ export const v2PushSecrets = async ({
|
|||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
// TODO: clean up function and fix up types
|
// TODO: clean up function and fix up types
|
||||||
const actions: IAction[] = [];
|
|
||||||
|
|
||||||
// construct useful data structures
|
// construct useful data structures
|
||||||
const oldSecrets = await getSecrets({
|
const oldSecrets = await getSecrets({
|
||||||
@@ -356,15 +351,6 @@ export const v2PushSecrets = async ({
|
|||||||
await EESecretService.markDeletedSecretVersions({
|
await EESecretService.markDeletedSecretVersions({
|
||||||
secretIds: toDelete,
|
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) => {
|
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
|
// 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
|
// (EE) take a secret snapshot
|
||||||
@@ -509,17 +478,6 @@ export const v2PushSecrets = async ({
|
|||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
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,
|
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;
|
return secrets;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,6 @@ import {
|
|||||||
UnauthorizedRequestError
|
UnauthorizedRequestError
|
||||||
} from "../utils/errors";
|
} from "../utils/errors";
|
||||||
import {
|
import {
|
||||||
ACTION_ADD_SECRETS,
|
|
||||||
ACTION_DELETE_SECRETS,
|
|
||||||
ACTION_READ_SECRETS,
|
|
||||||
ACTION_UPDATE_SECRETS,
|
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_BASE64,
|
ENCODING_SCHEME_BASE64,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
@@ -48,8 +44,8 @@ import {
|
|||||||
} from "../utils/crypto";
|
} from "../utils/crypto";
|
||||||
import { TelemetryService } from "../services";
|
import { TelemetryService } from "../services";
|
||||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||||
import { EEAuditLogService, EELogService, EESecretService } from "../ee/services";
|
import { EEAuditLogService, EESecretService } from "../ee/services";
|
||||||
import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/authn/helpers";
|
import { getAuthDataPayloadUserObj } from "../utils/authn/helpers";
|
||||||
import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService";
|
import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
@@ -478,23 +474,6 @@ export const createSecretHelper = async ({
|
|||||||
secretVersions: [secretVersion]
|
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(
|
await EEAuditLogService.createAuditLog(
|
||||||
authData,
|
authData,
|
||||||
{
|
{
|
||||||
@@ -597,23 +576,6 @@ export const getSecretsHelper = async ({
|
|||||||
.lean()
|
.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(
|
await EEAuditLogService.createAuditLog(
|
||||||
authData,
|
authData,
|
||||||
{
|
{
|
||||||
@@ -725,23 +687,6 @@ export const getSecretHelper = async ({
|
|||||||
|
|
||||||
if (!secret) throw SecretNotFoundError();
|
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(
|
await EEAuditLogService.createAuditLog(
|
||||||
authData,
|
authData,
|
||||||
{
|
{
|
||||||
@@ -945,23 +890,6 @@ export const updateSecretHelper = async ({
|
|||||||
secretVersions: [secretVersion]
|
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(
|
await EEAuditLogService.createAuditLog(
|
||||||
authData,
|
authData,
|
||||||
{
|
{
|
||||||
@@ -1089,23 +1017,6 @@ export const deleteSecretHelper = async ({
|
|||||||
secretIds: secrets.map((secret) => secret._id)
|
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(
|
await EEAuditLogService.createAuditLog(
|
||||||
authData,
|
authData,
|
||||||
{
|
{
|
||||||
@@ -1769,22 +1680,6 @@ export const deleteSecretBatchHelper = async ({
|
|||||||
secretIds: deletedSecrets.map((secret) => secret._id)
|
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(
|
await EEAuditLogService.createAuditLog(
|
||||||
authData,
|
authData,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ import {
|
|||||||
User,
|
User,
|
||||||
UserAction
|
UserAction
|
||||||
} from "../models";
|
} from "../models";
|
||||||
import {
|
|
||||||
Action,
|
|
||||||
Log
|
|
||||||
} from "../ee/models";
|
|
||||||
import { sendMail } from "./nodemailer";
|
import { sendMail } from "./nodemailer";
|
||||||
import {
|
import {
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
@@ -247,14 +243,6 @@ export const deleteUser = async ({
|
|||||||
user: user._id
|
user: user._id
|
||||||
});
|
});
|
||||||
|
|
||||||
await Action.deleteMany({
|
|
||||||
user: user._id
|
|
||||||
});
|
|
||||||
|
|
||||||
await Log.deleteMany({
|
|
||||||
user: user._id
|
|
||||||
});
|
|
||||||
|
|
||||||
await TokenVersion.deleteMany({
|
await TokenVersion.deleteMany({
|
||||||
user: user._id
|
user: user._id
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,11 +19,9 @@ import {
|
|||||||
Workspace
|
Workspace
|
||||||
} from "../models";
|
} from "../models";
|
||||||
import {
|
import {
|
||||||
Action,
|
|
||||||
AuditLog,
|
AuditLog,
|
||||||
FolderVersion,
|
FolderVersion,
|
||||||
IPType,
|
IPType,
|
||||||
Log,
|
|
||||||
SecretApprovalPolicy,
|
SecretApprovalPolicy,
|
||||||
SecretApprovalRequest,
|
SecretApprovalRequest,
|
||||||
SecretSnapshot,
|
SecretSnapshot,
|
||||||
@@ -193,14 +191,6 @@ export const deleteWorkspace = async ({
|
|||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
});
|
});
|
||||||
|
|
||||||
await Log.deleteMany({
|
|
||||||
workspace: workspace._id
|
|
||||||
});
|
|
||||||
|
|
||||||
await Action.deleteMany({
|
|
||||||
workspace: workspace._id
|
|
||||||
});
|
|
||||||
|
|
||||||
await SecretApprovalPolicy.deleteMany({
|
await SecretApprovalPolicy.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ const swaggerFile = require("../spec.json");
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
import { apiLimiter } from "./helpers/rateLimiter";
|
import { apiLimiter } from "./helpers/rateLimiter";
|
||||||
import {
|
import {
|
||||||
action as eeActionRouter,
|
|
||||||
cloudProducts as eeCloudProductsRouter,
|
cloudProducts as eeCloudProductsRouter,
|
||||||
organizations as eeOrganizationsRouter,
|
organizations as eeOrganizationsRouter,
|
||||||
sso as eeSSORouter,
|
sso as eeSSORouter,
|
||||||
@@ -193,7 +192,6 @@ const main = async () => {
|
|||||||
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
||||||
app.use("/api/v1/users", eeUsersRouter);
|
app.use("/api/v1/users", eeUsersRouter);
|
||||||
app.use("/api/v1/workspace", eeWorkspaceRouter);
|
app.use("/api/v1/workspace", eeWorkspaceRouter);
|
||||||
app.use("/api/v1/action", eeActionRouter);
|
|
||||||
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
||||||
app.use("/api/v1/sso", eeSSORouter);
|
app.use("/api/v1/sso", eeSSORouter);
|
||||||
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
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/secret", v2SecretRouter); // deprecate
|
||||||
app.use("/api/v2/secrets", v2SecretsRouter); // note: in the process of moving to v3/secrets
|
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-token", v2ServiceTokenDataRouter);
|
||||||
// app.use("/api/v2/service-accounts", v2ServiceAccountsRouter); // new
|
|
||||||
|
|
||||||
// v3 routes (experimental)
|
// v3 routes (experimental)
|
||||||
app.use("/api/v3/auth", v3AuthRouter);
|
app.use("/api/v3/auth", v3AuthRouter);
|
||||||
app.use("/api/v3/secrets", v3SecretsRouter);
|
app.use("/api/v3/secrets", v3SecretsRouter);
|
||||||
app.use("/api/v3/workspaces", v3WorkspacesRouter);
|
app.use("/api/v3/workspaces", v3WorkspacesRouter);
|
||||||
app.use("/api/v3/signup", v3SignupRouter);
|
app.use("/api/v3/signup", v3SignupRouter);
|
||||||
app.use("/api/v3/users", v3UsersRouter);
|
app.use("/api/v3/us", v3UsersRouter);
|
||||||
|
|
||||||
// api docs
|
// api docs
|
||||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
|
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 requireIntegrationAuthorizationAuth from "./requireIntegrationAuthorizationAuth";
|
||||||
import requireServiceTokenAuth from "./requireServiceTokenAuth";
|
import requireServiceTokenAuth from "./requireServiceTokenAuth";
|
||||||
import requireServiceTokenDataAuth from "./requireServiceTokenDataAuth";
|
import requireServiceTokenDataAuth from "./requireServiceTokenDataAuth";
|
||||||
import requireServiceAccountAuth from "./requireServiceAccountAuth";
|
|
||||||
import requireServiceAccountWorkspacePermissionAuth from "./requireServiceAccountWorkspacePermissionAuth";
|
|
||||||
import requireSecretAuth from "./requireSecretAuth";
|
import requireSecretAuth from "./requireSecretAuth";
|
||||||
import requireSecretsAuth from "./requireSecretsAuth";
|
import requireSecretsAuth from "./requireSecretsAuth";
|
||||||
import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled";
|
import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled";
|
||||||
@@ -32,8 +30,6 @@ export {
|
|||||||
requireIntegrationAuthorizationAuth,
|
requireIntegrationAuthorizationAuth,
|
||||||
requireServiceTokenAuth,
|
requireServiceTokenAuth,
|
||||||
requireServiceTokenDataAuth,
|
requireServiceTokenDataAuth,
|
||||||
requireServiceAccountAuth,
|
|
||||||
requireServiceAccountWorkspacePermissionAuth,
|
|
||||||
requireSecretAuth,
|
requireSecretAuth,
|
||||||
requireSecretsAuth,
|
requireSecretsAuth,
|
||||||
requireBlindIndicesEnabled,
|
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 "./secretImports";
|
||||||
export * from "./secretBlindIndexData";
|
export * from "./secretBlindIndexData";
|
||||||
export * from "./serviceToken"; // TODO: deprecate
|
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 "./tokenData";
|
||||||
export * from "./user";
|
export * from "./user";
|
||||||
export * from "./userAction";
|
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 organizations from "./organizations";
|
||||||
import secret from "./secret"; // deprecated
|
import secret from "./secret"; // deprecated
|
||||||
import secrets from "./secrets";
|
import secrets from "./secrets";
|
||||||
import serviceAccounts from "./serviceAccounts";
|
|
||||||
import serviceTokenData from "./serviceTokenData";
|
import serviceTokenData from "./serviceTokenData";
|
||||||
import signup from "./signup";
|
import signup from "./signup";
|
||||||
import tags from "./tags";
|
import tags from "./tags";
|
||||||
@@ -20,7 +19,6 @@ export {
|
|||||||
secret,
|
secret,
|
||||||
secrets,
|
secrets,
|
||||||
serviceTokenData,
|
serviceTokenData,
|
||||||
serviceAccounts,
|
|
||||||
environment,
|
environment,
|
||||||
tags,
|
tags,
|
||||||
membership
|
membership
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import {
|
import { requireAuth } from "../../middleware";
|
||||||
requireAuth,
|
import { AuthMode } from "../../variables";
|
||||||
requireOrganizationAuth
|
|
||||||
} from "../../middleware";
|
|
||||||
import { ACCEPTED, ADMIN, AuthMode } from "../../variables";
|
|
||||||
import { organizationsController } from "../../controllers/v2";
|
import { organizationsController } from "../../controllers/v2";
|
||||||
|
|
||||||
// TODO: /POST to create membership
|
// TODO: /POST to create membership
|
||||||
@@ -41,19 +38,6 @@ router.get(
|
|||||||
organizationsController.getOrganizationWorkspaces
|
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(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
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,
|
getTelemetryEnabled,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import {
|
import {
|
||||||
ServiceAccount,
|
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
User,
|
User,
|
||||||
} from "../models";
|
} from "../models";
|
||||||
@@ -56,16 +55,11 @@ class Telemetry {
|
|||||||
let distinctId = "";
|
let distinctId = "";
|
||||||
if (authData.authPayload instanceof User) {
|
if (authData.authPayload instanceof User) {
|
||||||
distinctId = authData.authPayload.email;
|
distinctId = authData.authPayload.email;
|
||||||
} else if (authData.authPayload instanceof ServiceAccount) {
|
|
||||||
distinctId = `sa.${authData.authPayload._id.toString()}`;
|
|
||||||
} else if (authData.authPayload instanceof ServiceTokenData) {
|
} else if (authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
|
||||||
if (authData.authPayload.user) {
|
if (authData.authPayload.user) {
|
||||||
const user = await User.findById(authData.authPayload.user, "email");
|
const user = await User.findById(authData.authPayload.user, "email");
|
||||||
if (!user) throw AccountNotFoundError();
|
if (!user) throw AccountNotFoundError();
|
||||||
distinctId = user.email;
|
distinctId = user.email;
|
||||||
} else if (authData.authPayload.serviceAccount) {
|
|
||||||
distinctId = distinctId = `sa.${authData.authPayload.serviceAccount.toString()}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
-1
@@ -29,7 +29,6 @@ declare global {
|
|||||||
secrets: any;
|
secrets: any;
|
||||||
secretSnapshot: any;
|
secretSnapshot: any;
|
||||||
serviceToken: any;
|
serviceToken: any;
|
||||||
serviceAccount: any;
|
|
||||||
accessToken: any;
|
accessToken: any;
|
||||||
accessId: any;
|
accessId: any;
|
||||||
serviceTokenData: any;
|
serviceTokenData: any;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { AuthData } from "../../../interfaces/middleware";
|
import { AuthData } from "../../../interfaces/middleware";
|
||||||
import {
|
import {
|
||||||
ServiceAccount,
|
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
ServiceTokenDataV3,
|
ServiceTokenDataV3,
|
||||||
User
|
User
|
||||||
@@ -16,10 +15,6 @@ import {
|
|||||||
return { userId: authData.authPayload._id };
|
return { userId: authData.authPayload._id };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authPayload instanceof ServiceAccount) {
|
|
||||||
return { serviceAccountId: authData.authPayload._id };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authPayload instanceof ServiceTokenData) {
|
||||||
return { serviceTokenDataId: authData.authPayload._id };
|
return { serviceTokenDataId: authData.authPayload._id };
|
||||||
}
|
}
|
||||||
@@ -39,10 +34,6 @@ export const getAuthDataPayloadUserObj = (authData: AuthData) => {
|
|||||||
return { user: authData.authPayload._id };
|
return { user: authData.authPayload._id };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authPayload instanceof ServiceAccount) {
|
|
||||||
return { user: authData.authPayload.user };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authPayload instanceof ServiceTokenData) {
|
||||||
return { user: authData.authPayload.user };
|
return { user: authData.authPayload.user };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,16 +182,6 @@ export const SecretSnapshotNotFoundError = (error?: Partial<RequestErrorContext>
|
|||||||
stack: error?.stack,
|
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]<-----
|
//* ----->[SERVICE TOKEN DATA ERRORS]<-----
|
||||||
export const ServiceTokenDataNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
export const ServiceTokenDataNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
@@ -212,25 +202,6 @@ export const APIKeyDataNotFoundError = (error?: Partial<RequestErrorContext>) =>
|
|||||||
stack: error?.stack,
|
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({
|
export const BotNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||||
statusCode: error?.statusCode ?? 404,
|
statusCode: error?.statusCode ?? 404,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ export * from "./membership";
|
|||||||
export * from "./membershipOrg";
|
export * from "./membershipOrg";
|
||||||
export * from "./organization";
|
export * from "./organization";
|
||||||
export * from "./secrets";
|
export * from "./secrets";
|
||||||
export * from "./serviceAccount";
|
|
||||||
export * from "./serviceTokenData";
|
export * from "./serviceTokenData";
|
||||||
export * from "./serviceTokenDataV3";
|
export * from "./serviceTokenDataV3";
|
||||||
export * from "./apiKeyDataV3";
|
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 fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { Types } from "mongoose";
|
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 { validateMembership } from "../helpers/membership";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { BadRequestError, UnauthorizedRequestError, ValidationError } from "../utils/errors";
|
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]
|
* Validate that user (client) can access organization [organization]
|
||||||
* @param {Object} obj
|
* @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 "./secretRotation";
|
||||||
export * from "./secrets";
|
export * from "./secrets";
|
||||||
export * from "./secretSnapshots";
|
export * from "./secretSnapshots";
|
||||||
export * from "./serviceAccounts";
|
|
||||||
export * from "./serviceTokens";
|
export * from "./serviceTokens";
|
||||||
export * from "./ssoConfig";
|
export * from "./ssoConfig";
|
||||||
export * from "./subscriptions";
|
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;
|
|
||||||
-389
@@ -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
@@ -1 +0,0 @@
|
|||||||
export { OrgServiceAccountsTable } from "./OrgServiceAccountsTable";
|
|
||||||
Reference in New Issue
Block a user