Add dashboard-specific secret and secret import audit logs, re-touch audit logs v2 UI

This commit is contained in:
Tuan Dang
2023-08-08 19:03:24 +07:00
parent 6cb8cf53f8
commit 0cad823267
19 changed files with 472 additions and 55 deletions
@@ -86,9 +86,15 @@ export const createIntegration = async (req: Request, res: Response) => {
integration: integration.integration, integration: integration.integration,
environment: integration.environment, environment: integration.environment,
secretPath, secretPath,
url: integration.url,
app: integration.app, app: integration.app,
appId: integration.appId,
targetEnvironment: integration.targetEnvironment, targetEnvironment: integration.targetEnvironment,
targetEnvironmentId: integration.targetEnvironmentId targetEnvironmentId: integration.targetEnvironmentId,
targetService: integration.targetService,
targetServiceId: integration.targetServiceId,
path: integration.path,
region: integration.region
} }
}, },
{ {
@@ -192,9 +198,15 @@ export const deleteIntegration = async (req: Request, res: Response) => {
integration: integration.integration, integration: integration.integration,
environment: integration.environment, environment: integration.environment,
secretPath: integration.secretPath, secretPath: integration.secretPath,
url: integration.url,
app: integration.app, app: integration.app,
appId: integration.appId,
targetEnvironment: integration.targetEnvironment, targetEnvironment: integration.targetEnvironment,
targetEnvironmentId: integration.targetEnvironmentId targetEnvironmentId: integration.targetEnvironmentId,
targetService: integration.targetService,
targetServiceId: integration.targetServiceId,
path: integration.path,
region: integration.region
} }
}, },
{ {
@@ -1,6 +1,9 @@
import { Types } from "mongoose";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Key } from "../../models"; import { Key } from "../../models";
import { findMembership } from "../../helpers/membership"; import { findMembership } from "../../helpers/membership";
import { EventType } from "../../ee/models";
import { EEAuditLogService } from "../../ee/services";
/** /**
* Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with * Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with
@@ -58,6 +61,18 @@ export const getLatestKey = async (req: Request, res: Response) => {
if (latestKey.length > 0) { if (latestKey.length > 0) {
resObj["latestKey"] = latestKey[0]; resObj["latestKey"] = latestKey[0];
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.GET_WORKSPACE_KEY,
metadata: {
keyId: latestKey[0]._id.toString()
}
},
{
workspaceId: new Types.ObjectId(workspaceId)
}
);
} }
return res.status(200).send(resObj); return res.status(200).send(resObj);
@@ -4,6 +4,8 @@ import SecretImport from "../../models/secretImports";
import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getAllImportedSecrets } from "../../services/SecretImportService";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { ADMIN, MEMBER } from "../../variables"; import { ADMIN, MEMBER } from "../../variables";
import { EEAuditLogService } from "../../ee/services";
import { EventType } from "../../ee/models";
export const createSecretImport = async (req: Request, res: Response) => { export const createSecretImport = async (req: Request, res: Response) => {
const { workspaceId, environment, folderId, secretImport } = req.body; const { workspaceId, environment, folderId, secretImport } = req.body;
@@ -20,7 +22,24 @@ export const createSecretImport = async (req: Request, res: Response) => {
folderId, folderId,
imports: [{ environment: secretImport.environment, secretPath: secretImport.secretPath }] imports: [{ environment: secretImport.environment, secretPath: secretImport.secretPath }]
}); });
await doc.save(); await doc.save();
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.CREATE_SECRET_IMPORT,
metadata: {
environment,
secretImportId: doc._id.toString(),
folderId: doc.folderId.toString(),
importEnvironment: secretImport.environment,
importSecretPath: secretImport.secretPath
}
},
{
workspaceId: doc.workspace
}
);
return res.status(200).json({ message: "successfully created secret import" }); return res.status(200).json({ message: "successfully created secret import" });
} }
@@ -30,11 +49,29 @@ export const createSecretImport = async (req: Request, res: Response) => {
if (doesImportExist) { if (doesImportExist) {
throw BadRequestError({ message: "Secret import already exist" }); throw BadRequestError({ message: "Secret import already exist" });
} }
importSecDoc.imports.push({ importSecDoc.imports.push({
environment: secretImport.environment, environment: secretImport.environment,
secretPath: secretImport.secretPath secretPath: secretImport.secretPath
}); });
await importSecDoc.save(); await importSecDoc.save();
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.CREATE_SECRET_IMPORT,
metadata: {
environment,
secretImportId: importSecDoc._id.toString(),
folderId: importSecDoc.folderId.toString(),
importEnvironment: secretImport.environment,
importSecretPath: secretImport.secretPath
}
},
{
workspaceId: importSecDoc.workspace
}
);
return res.status(200).json({ message: "successfully created secret import" }); return res.status(200).json({ message: "successfully created secret import" });
}; };
@@ -56,6 +93,21 @@ export const updateSecretImport = async (req: Request, res: Response) => {
importSecDoc.imports = secretImports; importSecDoc.imports = secretImports;
await importSecDoc.save(); await importSecDoc.save();
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.UPDATE_SECRET_IMPORT,
metadata: {
environment: importSecDoc.environment,
secretImportId: importSecDoc._id.toString(),
folderId: importSecDoc.folderId.toString(),
numberOfImports: secretImports.length
}
},
{
workspaceId: importSecDoc.workspace
}
);
return res.status(200).json({ message: "successfully updated secret import" }); return res.status(200).json({ message: "successfully updated secret import" });
}; };
@@ -77,6 +129,24 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
!(environment === secretImportEnv && secretPath === secretImportPath) !(environment === secretImportEnv && secretPath === secretImportPath)
); );
await importSecDoc.save(); await importSecDoc.save();
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.DELETE_SECRET_IMPORT,
metadata: {
environment: importSecDoc.environment,
secretImportId: importSecDoc._id.toString(),
folderId: importSecDoc.folderId.toString(),
importEnvironment: secretImportEnv,
importSecretPath: secretImportPath
}
},
{
workspaceId: importSecDoc.workspace
}
);
return res.status(200).json({ message: "successfully delete secret import" }); return res.status(200).json({ message: "successfully delete secret import" });
}; };
@@ -111,6 +181,22 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
return res.status(200).json({ secrets: [] }); return res.status(200).json({ secrets: [] });
} }
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.GET_SECRET_IMPORTS,
metadata: {
environment,
secretImportId: importSecDoc._id.toString(),
folderId,
numberOfImports: importSecDoc.imports.length
}
},
{
workspaceId: importSecDoc.workspace
}
);
const secrets = await getAllImportedSecrets(workspaceId, environment, folderId); const secrets = await getAllImportedSecrets(workspaceId, environment, folderId);
return res.status(200).json({ secrets }); return res.status(200).json({ secrets });
}; };
@@ -1,7 +1,7 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { ISecret, Secret, ServiceTokenData } from "../../models"; import { ISecret, Secret, ServiceTokenData } from "../../models";
import { IAction, SecretVersion } from "../../ee/models"; import { IAction, SecretVersion, EventType, AuditLog } from "../../ee/models";
import { import {
ACTION_ADD_SECRETS, ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS, ACTION_DELETE_SECRETS,
@@ -14,7 +14,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 { EELogService, EESecretService } from "../../ee/services"; import { EELogService, EESecretService, EEAuditLogService } 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";
@@ -56,12 +56,13 @@ export const batchSecrets = async (req: Request, res: Response) => {
environment: string; environment: string;
requests: BatchSecretRequest[]; requests: BatchSecretRequest[];
} = req.body; } = req.body;
let secretPath = req.body.secretPath as string; let secretPath = req.body.secretPath as string;
let folderId = req.body.folderId as string; let folderId = req.body.folderId as string;
const createSecrets: BatchSecret[] = []; const createSecrets: BatchSecret[] = [];
const updateSecrets: BatchSecret[] = []; const updateSecrets: BatchSecret[] = [];
const deleteSecrets: Types.ObjectId[] = []; const deleteSecrets: { _id: Types.ObjectId, secretName: string; }[] = [];
const actions: IAction[] = []; const actions: IAction[] = [];
// get secret blind index salt // get secret blind index salt
@@ -133,7 +134,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
}); });
break; break;
case "DELETE": case "DELETE":
deleteSecrets.push(new Types.ObjectId(request.secret._id)); deleteSecrets.push({ _id: new Types.ObjectId(request.secret._id), secretName: request.secret.secretName });
break; break;
} }
} }
@@ -154,6 +155,30 @@ export const batchSecrets = async (req: Request, res: Response) => {
}) })
}); });
const auditLogs = await Promise.all(
createdSecrets.map((secret, index) => {
return EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.CREATE_SECRET,
metadata: {
environment: secret.environment,
secretPath: secretPath ?? "/",
secretId: secret._id.toString(),
secretKey: createSecrets[index].secretName,
secretVersion: secret.version
}
},
{
workspaceId: secret.workspace
},
false
);
})
);
await AuditLog.insertMany(auditLogs);
const addAction = (await EELogService.createAction({ const addAction = (await EELogService.createAction({
name: ACTION_ADD_SECRETS, name: ACTION_ADD_SECRETS,
userId: req.user?._id, userId: req.user?._id,
@@ -253,6 +278,30 @@ export const batchSecrets = async (req: Request, res: Response) => {
} }
}); });
const auditLogs = await Promise.all(
updateSecrets.map((secret) => {
return EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.UPDATE_SECRET,
metadata: {
environment,
secretPath: secretPath ?? "/",
secretId: secret._id.toString(),
secretKey: secret.secretName,
secretVersion: listedSecretsObj[secret._id.toString()].version
}
},
{
workspaceId: new Types.ObjectId(workspaceId)
},
false
);
})
);
await AuditLog.insertMany(auditLogs);
const updateAction = (await EELogService.createAction({ const updateAction = (await EELogService.createAction({
name: ACTION_UPDATE_SECRETS, name: ACTION_UPDATE_SECRETS,
userId: req.user._id, userId: req.user._id,
@@ -279,21 +328,60 @@ export const batchSecrets = async (req: Request, res: Response) => {
// handle delete secrets // handle delete secrets
if (deleteSecrets.length > 0) { if (deleteSecrets.length > 0) {
const deleteSecretIds: Types.ObjectId[] = deleteSecrets.map((s) => s._id);
const deletedSecretsObj = (await Secret.find({
_id: {
$in: deleteSecretIds
}
}))
.reduce(
(obj: any, secret: ISecret) => ({
...obj,
[secret._id.toString()]: secret
}),
{}
);
await Secret.deleteMany({ await Secret.deleteMany({
_id: { _id: {
$in: deleteSecrets $in: deleteSecretIds
} }
}); });
await EESecretService.markDeletedSecretVersions({ await EESecretService.markDeletedSecretVersions({
secretIds: deleteSecrets secretIds: deleteSecretIds
}); });
const auditLogs = await Promise.all(
deleteSecrets.map((secret) => {
return EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.DELETE_SECRET,
metadata: {
environment,
secretPath: secretPath ?? "/",
secretId: secret._id.toString(),
secretKey: secret.secretName,
secretVersion: deletedSecretsObj[secret._id.toString()].version
}
},
{
workspaceId: new Types.ObjectId(workspaceId)
},
false
);
})
);
await AuditLog.insertMany(auditLogs);
const deleteAction = (await EELogService.createAction({ const deleteAction = (await EELogService.createAction({
name: ACTION_DELETE_SECRETS, name: ACTION_DELETE_SECRETS,
userId: req.user._id, userId: req.user._id,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
secretIds: deleteSecrets secretIds: deleteSecretIds
})) as IAction; })) as IAction;
actions.push(deleteAction); actions.push(deleteAction);
@@ -351,7 +439,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
} }
if (deleteSecrets.length > 0) { if (deleteSecrets.length > 0) {
resObj["deletedSecrets"] = deleteSecrets.map((d) => d.toString()); resObj["deletedSecrets"] = deleteSecrets.map((d) => d._id.toString());
} }
return res.status(200).send(resObj); return res.status(200).send(resObj);
@@ -9,6 +9,8 @@ import {
import { pushKeys } from "../../helpers/key"; import { pushKeys } from "../../helpers/key";
import { EventService, TelemetryService } from "../../services"; import { EventService, TelemetryService } from "../../services";
import { eventPushSecrets } from "../../events"; import { eventPushSecrets } from "../../events";
import { EEAuditLogService } from "../../ee/services";
import { EventType } from "../../ee/models";
interface V2PushSecret { interface V2PushSecret {
type: string; // personal or shared type: string; // personal or shared
@@ -188,8 +190,22 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
if (!key) throw new Error("Failed to find workspace key"); if (!key) throw new Error("Failed to find workspace key");
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.GET_WORKSPACE_KEY,
metadata: {
keyId: key._id.toString()
}
},
{
workspaceId: new Types.ObjectId(workspaceId)
}
);
return res.status(200).json(key); return res.status(200).json(key);
}; };
export const getWorkspaceServiceTokenData = async (req: Request, res: Response) => { export const getWorkspaceServiceTokenData = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const { workspaceId } = req.params;
+6 -1
View File
@@ -17,6 +17,7 @@ export enum EventType {
CREATE_SECRET = "create-secret", CREATE_SECRET = "create-secret",
UPDATE_SECRET = "update-secret", UPDATE_SECRET = "update-secret",
DELETE_SECRET = "delete-secret", DELETE_SECRET = "delete-secret",
GET_WORKSPACE_KEY = "get-workspace-key",
AUTHORIZE_INTEGRATION = "authorize-integration", AUTHORIZE_INTEGRATION = "authorize-integration",
UNAUTHORIZE_INTEGRATION = "unauthorize-integration", UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
CREATE_INTEGRATION = "create-integration", CREATE_INTEGRATION = "create-integration",
@@ -36,5 +37,9 @@ export enum EventType {
DELETE_FOLDER = "delete-folder", DELETE_FOLDER = "delete-folder",
CREATE_WEBHOOK = "create-webhook", CREATE_WEBHOOK = "create-webhook",
UPDATE_WEBHOOK_STATUS = "update-webhook-status", UPDATE_WEBHOOK_STATUS = "update-webhook-status",
DELETE_WEBHOOK = "delete-webhook" DELETE_WEBHOOK = "delete-webhook",
GET_SECRET_IMPORTS = "get-secret-imports",
CREATE_SECRET_IMPORT = "create-secret-import",
UPDATE_SECRET_IMPORT = "update-secret-import",
DELETE_SECRET_IMPORT = "delete-secret-import",
} }
+70 -4
View File
@@ -80,17 +80,24 @@ interface DeleteSecretEvent {
} }
} }
interface GetWorkspaceKeyEvent {
type: EventType.GET_WORKSPACE_KEY,
metadata: {
keyId: string;
}
}
interface AuthorizeIntegrationEvent { interface AuthorizeIntegrationEvent {
type: EventType.AUTHORIZE_INTEGRATION; type: EventType.AUTHORIZE_INTEGRATION;
metadata: { metadata: {
integration: string; // TODO: fix type integration: string;
} }
} }
interface UnauthorizeIntegrationEvent { interface UnauthorizeIntegrationEvent {
type: EventType.UNAUTHORIZE_INTEGRATION; type: EventType.UNAUTHORIZE_INTEGRATION;
metadata: { metadata: {
integration: string; // TODO: fix type integration: string;
} }
} }
@@ -101,9 +108,15 @@ interface CreateIntegrationEvent {
integration: string; // TODO: fix type integration: string; // TODO: fix type
environment: string; environment: string;
secretPath: string; secretPath: string;
url?: string;
app?: string; app?: string;
appId?: string;
targetEnvironment?: string; targetEnvironment?: string;
targetEnvironmentId?: string; // TODO: consider adding other vars targetEnvironmentId?: string;
targetService?: string;
targetServiceId?: string;
path?: string;
region?: string;
} }
} }
@@ -114,9 +127,15 @@ interface DeleteIntegrationEvent {
integration: string; // TODO: fix type integration: string; // TODO: fix type
environment: string; environment: string;
secretPath: string; secretPath: string;
url?: string;
app?: string; app?: string;
appId?: string;
targetEnvironment?: string; targetEnvironment?: string;
targetEnvironmentId?: string; targetEnvironmentId?: string;
targetService?: string;
targetServiceId?: string;
path?: string;
region?: string;
} }
} }
@@ -275,12 +294,55 @@ interface DeleteWebhookEvent {
} }
} }
interface GetSecretImportsEvent {
type: EventType.GET_SECRET_IMPORTS,
metadata: {
environment: string;
secretImportId: string;
folderId: string;
numberOfImports: number;
}
}
interface CreateSecretImportEvent {
type: EventType.CREATE_SECRET_IMPORT,
metadata: {
environment: string;
secretImportId: string;
folderId: string;
importEnvironment: string;
importSecretPath: string;
}
}
interface UpdateSecretImportEvent {
type: EventType.UPDATE_SECRET_IMPORT,
metadata: {
environment: string;
secretImportId: string;
folderId: string;
numberOfImports: number;
}
}
interface DeleteSecretImportEvent {
type: EventType.DELETE_SECRET_IMPORT,
metadata: {
environment: string;
secretImportId: string;
folderId: string;
importEnvironment: string;
importSecretPath: string;
}
}
export type Event = export type Event =
| GetSecretsEvent | GetSecretsEvent
| GetSecretEvent | GetSecretEvent
| CreateSecretEvent | CreateSecretEvent
| UpdateSecretEvent | UpdateSecretEvent
| DeleteSecretEvent | DeleteSecretEvent
| GetWorkspaceKeyEvent
| AuthorizeIntegrationEvent | AuthorizeIntegrationEvent
| UnauthorizeIntegrationEvent | UnauthorizeIntegrationEvent
| CreateIntegrationEvent | CreateIntegrationEvent
@@ -300,4 +362,8 @@ export type Event =
| DeleteFolderEvent | DeleteFolderEvent
| CreateWebhookEvent | CreateWebhookEvent
| UpdateWebhookStatusEvent | UpdateWebhookStatusEvent
| DeleteWebhookEvent; | DeleteWebhookEvent
| GetSecretImportsEvent
| CreateSecretImportEvent
| UpdateSecretImportEvent
| DeleteSecretImportEvent;
+6 -2
View File
@@ -16,7 +16,7 @@ type ValidEventScope =
| Required<EventScope> | Required<EventScope>
export default class EEAuditLogService { export default class EEAuditLogService {
static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope) { static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope, shouldSave: boolean = true) {
const MS_IN_DAY = 24 * 60 * 60 * 1000; const MS_IN_DAY = 24 * 60 * 60 * 1000;
@@ -39,7 +39,11 @@ export default class EEAuditLogService {
userAgent: authData.userAgent, userAgent: authData.userAgent,
userAgentType: authData.userAgentType, userAgentType: authData.userAgentType,
expiresAt: new Date(Date.now() + ttl) expiresAt: new Date(Date.now() + ttl)
}).save(); });
if (shouldSave) {
await auditLog.save();
}
return auditLog; return auditLog;
} }
+1
View File
@@ -30,6 +30,7 @@ export interface BatchSecretRequest {
export interface BatchSecret { export interface BatchSecret {
_id: string; _id: string;
type: "shared" | "personal"; type: "shared" | "personal";
secretName: string;
secretBlindIndex: string; secretBlindIndex: string;
secretKeyCiphertext: string; secretKeyCiphertext: string;
secretKeyIV: string; secretKeyIV: string;
@@ -1,11 +1,12 @@
import { EventType, UserAgentType } from "./enums"; import { EventType, UserAgentType } from "./enums";
export const eventToNameMap: { [K in EventType]: string } = { export const eventToNameMap: { [K in EventType]: string } = {
[EventType.GET_SECRETS]: "Get secrets", [EventType.GET_SECRETS]: "List secrets",
[EventType.GET_SECRET]: "Get secret", [EventType.GET_SECRET]: "Read secret",
[EventType.CREATE_SECRET]: "Create secret", [EventType.CREATE_SECRET]: "Create secret",
[EventType.UPDATE_SECRET]: "Update secret", [EventType.UPDATE_SECRET]: "Update secret",
[EventType.DELETE_SECRET]: "Delete secret", [EventType.DELETE_SECRET]: "Delete secret",
[EventType.GET_WORKSPACE_KEY]: "Read project key",
[EventType.AUTHORIZE_INTEGRATION]: "Authorize integration", [EventType.AUTHORIZE_INTEGRATION]: "Authorize integration",
[EventType.UNAUTHORIZE_INTEGRATION]: "Unauthorize integration", [EventType.UNAUTHORIZE_INTEGRATION]: "Unauthorize integration",
[EventType.CREATE_INTEGRATION]: "Create integration", [EventType.CREATE_INTEGRATION]: "Create integration",
@@ -26,6 +27,11 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.CREATE_WEBHOOK]: "Create webhook", [EventType.CREATE_WEBHOOK]: "Create webhook",
[EventType.UPDATE_WEBHOOK_STATUS]: "Update webhook status", [EventType.UPDATE_WEBHOOK_STATUS]: "Update webhook status",
[EventType.DELETE_WEBHOOK]: "Delete webhook", [EventType.DELETE_WEBHOOK]: "Delete webhook",
[EventType.GET_SECRET_IMPORTS]: "List secret imports",
[EventType.CREATE_SECRET_IMPORT]: "Create secret import",
[EventType.UPDATE_SECRET_IMPORT]: "Update secret import",
[EventType.DELETE_SECRET_IMPORT]: "Delete secret import",
}; };
export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = { export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = {
+6 -1
View File
@@ -16,6 +16,7 @@ export enum EventType {
CREATE_SECRET = "create-secret", CREATE_SECRET = "create-secret",
UPDATE_SECRET = "update-secret", UPDATE_SECRET = "update-secret",
DELETE_SECRET = "delete-secret", DELETE_SECRET = "delete-secret",
GET_WORKSPACE_KEY = "get-workspace-key",
AUTHORIZE_INTEGRATION = "authorize-integration", AUTHORIZE_INTEGRATION = "authorize-integration",
UNAUTHORIZE_INTEGRATION = "unauthorize-integration", UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
CREATE_INTEGRATION = "create-integration", CREATE_INTEGRATION = "create-integration",
@@ -35,5 +36,9 @@ export enum EventType {
DELETE_FOLDER = "delete-folder", DELETE_FOLDER = "delete-folder",
CREATE_WEBHOOK = "create-webhook", CREATE_WEBHOOK = "create-webhook",
UPDATE_WEBHOOK_STATUS = "update-webhook-status", UPDATE_WEBHOOK_STATUS = "update-webhook-status",
DELETE_WEBHOOK = "delete-webhook" DELETE_WEBHOOK = "delete-webhook",
GET_SECRET_IMPORTS = "get-secret-imports",
CREATE_SECRET_IMPORT = "create-secret-import",
UPDATE_SECRET_IMPORT = "update-secret-import",
DELETE_SECRET_IMPORT = "delete-secret-import"
} }
+72 -6
View File
@@ -82,17 +82,24 @@ interface DeleteSecretEvent {
} }
} }
interface GetWorkspaceKeyEvent {
type: EventType.GET_WORKSPACE_KEY,
metadata: {
keyId: string;
}
}
interface AuthorizeIntegrationEvent { interface AuthorizeIntegrationEvent {
type: EventType.AUTHORIZE_INTEGRATION; type: EventType.AUTHORIZE_INTEGRATION;
metadata: { metadata: {
integration: string; // TODO: fix type integration: string;
} }
} }
interface UnauthorizeIntegrationEvent { interface UnauthorizeIntegrationEvent {
type: EventType.UNAUTHORIZE_INTEGRATION; type: EventType.UNAUTHORIZE_INTEGRATION;
metadata: { metadata: {
integration: string; // TODO: fix type integration: string;
} }
} }
@@ -100,12 +107,18 @@ interface CreateIntegrationEvent {
type: EventType.CREATE_INTEGRATION; type: EventType.CREATE_INTEGRATION;
metadata: { metadata: {
integrationId: string; integrationId: string;
integration: string; // TODO: fix type integration: string;
environment: string; environment: string;
secretPath: string; secretPath: string;
url?: string;
app?: string; app?: string;
appId?: string;
targetEnvironment?: string; targetEnvironment?: string;
targetEnvironmentId?: string; // TODO: consider adding other vars targetEnvironmentId?: string;
targetService?: string;
targetServiceId?: string;
path?: string;
region?: string;
} }
} }
@@ -113,12 +126,18 @@ interface DeleteIntegrationEvent {
type: EventType.DELETE_INTEGRATION; type: EventType.DELETE_INTEGRATION;
metadata: { metadata: {
integrationId: string; integrationId: string;
integration: string; // TODO: fix type integration: string;
environment: string; environment: string;
secretPath: string; secretPath: string;
url?: string;
app?: string; app?: string;
appId?: string;
targetEnvironment?: string; targetEnvironment?: string;
targetEnvironmentId?: string; targetEnvironmentId?: string;
targetService?: string;
targetServiceId?: string;
path?: string;
region?: string;
} }
} }
@@ -277,12 +296,55 @@ interface DeleteWebhookEvent {
} }
} }
interface GetSecretImportsEvent {
type: EventType.GET_SECRET_IMPORTS,
metadata: {
environment: string;
secretImportId: string;
folderId: string;
numberOfImports: number;
}
}
interface CreateSecretImportEvent {
type: EventType.CREATE_SECRET_IMPORT,
metadata: {
environment: string;
secretImportId: string;
folderId: string;
importEnvironment: string;
importSecretPath: string;
}
}
interface UpdateSecretImportEvent {
type: EventType.UPDATE_SECRET_IMPORT,
metadata: {
environment: string;
secretImportId: string;
folderId: string;
numberOfImports: number;
}
}
interface DeleteSecretImportEvent {
type: EventType.DELETE_SECRET_IMPORT,
metadata: {
environment: string;
secretImportId: string;
folderId: string;
importEnvironment: string;
importSecretPath: string;
}
}
export type Event = export type Event =
| GetSecretsEvent | GetSecretsEvent
| GetSecretEvent | GetSecretEvent
| CreateSecretEvent | CreateSecretEvent
| UpdateSecretEvent | UpdateSecretEvent
| DeleteSecretEvent | DeleteSecretEvent
| GetWorkspaceKeyEvent
| AuthorizeIntegrationEvent | AuthorizeIntegrationEvent
| UnauthorizeIntegrationEvent | UnauthorizeIntegrationEvent
| CreateIntegrationEvent | CreateIntegrationEvent
@@ -302,7 +364,11 @@ export type Event =
| DeleteFolderEvent | DeleteFolderEvent
| CreateWebhookEvent | CreateWebhookEvent
| UpdateWebhookStatusEvent | UpdateWebhookStatusEvent
| DeleteWebhookEvent; | DeleteWebhookEvent
| GetSecretImportsEvent
| CreateSecretImportEvent
| UpdateSecretImportEvent
| DeleteSecretImportEvent;
export type AuditLog = { export type AuditLog = {
_id: string; _id: string;
+1 -1
View File
@@ -79,7 +79,7 @@ export type UpdateSecretArg = {
export type CreateSecretArg = Omit<UpdateSecretArg, "_id">; export type CreateSecretArg = Omit<UpdateSecretArg, "_id">;
export type DeleteSecretArg = { _id: string }; export type DeleteSecretArg = { _id: string, secretName: string; };
export type BatchSecretDTO = { export type BatchSecretDTO = {
workspaceId: string; workspaceId: string;
@@ -149,7 +149,7 @@ export const DashboardPage = () => {
const [snapshotId, setSnaphotId] = useState<string | null>(null); const [snapshotId, setSnaphotId] = useState<string | null>(null);
const [selectedEnv, setSelectedEnv] = useState<WorkspaceEnv | null>(null); const [selectedEnv, setSelectedEnv] = useState<WorkspaceEnv | null>(null);
const [sortDir, setSortDir] = useState<"asc" | "desc">("asc"); const [sortDir, setSortDir] = useState<"asc" | "desc">("asc");
const deletedSecretIds = useRef<string[]>([]); const deletedSecretIds = useRef<{ id: string; secretName: string; }[]>([]);
const { hasUnsavedChanges, setHasUnsavedChanges } = useLeaveConfirm({ initialValue: false }); const { hasUnsavedChanges, setHasUnsavedChanges } = useLeaveConfirm({ initialValue: false });
const folderId = router.query.folderId as string; const folderId = router.query.folderId as string;
@@ -498,9 +498,15 @@ export const DashboardPage = () => {
// record all deleted ids // record all deleted ids
// This will make final deletion easier // This will make final deletion easier
const onSecretDelete = useCallback((index: number, id?: string, overrideId?: string) => { const onSecretDelete = useCallback((index: number, secretName: string, id?: string, overrideId?: string) => {
if (id) deletedSecretIds.current.push(id); if (id) deletedSecretIds.current.push({
if (overrideId) deletedSecretIds.current.push(overrideId); id,
secretName
});
if (overrideId) deletedSecretIds.current.push({
id: overrideId,
secretName
});
remove(index); remove(index);
// just the case if this is called from drawer // just the case if this is called from drawer
handlePopUpClose("secretDetails"); handlePopUpClose("secretDetails");
@@ -182,15 +182,18 @@ const deepCompareSecrets = (lhs: DecryptedSecret, rhs: any) =>
JSON.stringify(lhs.tags) === JSON.stringify(rhs.tags); JSON.stringify(lhs.tags) === JSON.stringify(rhs.tags);
export const transformSecretsToBatchSecretReq = ( export const transformSecretsToBatchSecretReq = (
deletedSecretIds: string[], deletedSecretIds: { id: string; secretName: string; }[],
latestFileKey: any, latestFileKey: any,
secrets: FormData["secrets"], secrets: FormData["secrets"],
intialValues: DecryptedSecret[] = [] intialValues: DecryptedSecret[] = []
) => { ) => {
// deleted secrets // deleted secrets
const secretsToBeDeleted: BatchSecretDTO["requests"] = deletedSecretIds.map((id) => ({ const secretsToBeDeleted: BatchSecretDTO["requests"] = deletedSecretIds.map(({ id, secretName }) => ({
method: "DELETE", method: "DELETE",
secret: { _id: id } secret: {
_id: id,
secretName
}
})); }));
const secretsToBeUpdated: BatchSecretDTO["requests"] = []; const secretsToBeUpdated: BatchSecretDTO["requests"] = [];
@@ -261,7 +264,7 @@ export const transformSecretsToBatchSecretReq = (
if (idOverride) { if (idOverride) {
// if action is deleted meaning override has been removed but id is kept to collect at this point // if action is deleted meaning override has been removed but id is kept to collect at this point
if (overrideAction === SecretActionType.Deleted) { if (overrideAction === SecretActionType.Deleted) {
secretsToBeDeleted.push({ method: "DELETE", secret: { _id: idOverride } }); secretsToBeDeleted.push({ method: "DELETE", secret: { _id: idOverride, secretName: key } });
} else { } else {
// if not deleted action then as id is there its an updated // if not deleted action then as id is there its an updated
const initialSecretValue = intialValues?.find(({ _id: secId }) => secId === _id)!; const initialSecretValue = intialValues?.find(({ _id: secId }) => secId === _id)!;
@@ -67,7 +67,7 @@ type Props = {
isSecretValueHidden: boolean; isSecretValueHidden: boolean;
searchTerm: string; searchTerm: string;
// to record the ids of deleted ones // to record the ids of deleted ones
onSecretDelete: (index: number, id?: string, overrideId?: string) => void; onSecretDelete: (index: number, secretName: string, id?: string, overrideId?: string) => void;
// sidebar control props // sidebar control props
onRowExpand: (secId: string | undefined, index: number) => void; onRowExpand: (secId: string | undefined, index: number) => void;
// tag props // tag props
@@ -495,7 +495,9 @@ export const SecretInputRow = memo(
colorSchema="danger" colorSchema="danger"
ariaLabel="delete" ariaLabel="delete"
isDisabled={isReadOnly || isRollbackMode} isDisabled={isReadOnly || isRollbackMode}
onClick={() => onSecretDelete(index, secId, idOverride)} onClick={() => {
onSecretDelete(index, secKey, secId, idOverride);
}}
> >
<FontAwesomeIcon icon={faXmark} /> <FontAwesomeIcon icon={faXmark} />
</IconButton> </IconButton>
@@ -73,7 +73,7 @@ export const LogsFilter = ({
className="w-40 mr-4" className="w-40 mr-4"
> >
<Select <Select
defaultValue={field.value} {...(field.value ? { value: field.value } : { placeholder: "Select" })}
{...field} {...field}
onValueChange={(e) => onChange(e)} onValueChange={(e) => onChange(e)}
className="w-full" className="w-full"
@@ -99,7 +99,7 @@ export const LogsFilter = ({
className="w-40 mr-4" className="w-40 mr-4"
> >
<Select <Select
defaultValue={field.value} {...(field.value ? { value: field.value } : { placeholder: "Select" })}
{...field} {...field}
onValueChange={(e) => onChange(e)} onValueChange={(e) => onChange(e)}
className="w-full" className="w-full"
@@ -121,7 +121,7 @@ export const LogsFilter = ({
className="w-40 mr-4" className="w-40 mr-4"
> >
<Select <Select
defaultValue={field.value} {...(field.value ? { value: field.value } : { placeholder: "Select" })}
{...field} {...field}
onValueChange={(e) => onChange(e)} onValueChange={(e) => onChange(e)}
className="w-full" className="w-full"
@@ -189,7 +189,7 @@ export const LogsFilter = ({
}} }}
/> />
</div> </div>
<div> {/* <div>
<Button <Button
isLoading={false} isLoading={false}
colorSchema="primary" colorSchema="primary"
@@ -198,15 +198,15 @@ export const LogsFilter = ({
leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />} leftIcon={<FontAwesomeIcon icon={faFilterCircleXmark} className="mr-2" />}
onClick={() => reset({ onClick={() => reset({
eventType: undefined, eventType: undefined,
actor: "", actor: undefined,
userAgentType: "", userAgentType: undefined,
startDate: undefined, startDate: undefined,
endDate: undefined endDate: undefined
})} })}
> >
Clear filters Clear filters
</Button> </Button>
</div> </div> */}
</div> </div>
); );
} }
@@ -32,12 +32,14 @@ export const LogsSection = () => {
const perPage = watch("perPage") as number; const perPage = watch("perPage") as number;
return ( return (
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"> <div
<div className="flex items-center mb-8"> // className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
>
{/* <div className="flex items-center mb-8">
<h2 className="text-xl font-semibold flex-1 text-white"> <h2 className="text-xl font-semibold flex-1 text-white">
Audit Logs Audit Logs
</h2> </h2>
</div> </div> */}
<LogsFilter <LogsFilter
control={control} control={control}
reset={reset} reset={reset}
@@ -100,6 +100,9 @@ export const LogsTableRow = ({
{event.metadata.app && ( {event.metadata.app && (
<p>{`Target app: ${event.metadata.app}`}</p> <p>{`Target app: ${event.metadata.app}`}</p>
)} )}
{event.metadata.appId && (
<p>{`Target app: ${event.metadata.appId}`}</p>
)}
{event.metadata.targetEnvironment && ( {event.metadata.targetEnvironment && (
<p>{`Target environment: ${event.metadata.targetEnvironment}`}</p> <p>{`Target environment: ${event.metadata.targetEnvironment}`}</p>
)} )}
@@ -117,6 +120,9 @@ export const LogsTableRow = ({
{event.metadata.app && ( {event.metadata.app && (
<p>{`Target App: ${event.metadata.app}`}</p> <p>{`Target App: ${event.metadata.app}`}</p>
)} )}
{event.metadata.appId && (
<p>{`Target app: ${event.metadata.appId}`}</p>
)}
{event.metadata.targetEnvironment && ( {event.metadata.targetEnvironment && (
<p>{`Target environment: ${event.metadata.targetEnvironment}`}</p> <p>{`Target environment: ${event.metadata.targetEnvironment}`}</p>
)} )}
@@ -219,7 +225,7 @@ export const LogsTableRow = ({
return ( return (
<Td> <Td>
<p>{`Environment: ${event.metadata.environment}`}</p> <p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Secret Path: ${event.metadata.secretPath}`}</p> <p>{`Secret path: ${event.metadata.secretPath}`}</p>
<p>{`Webhook URL: ${event.metadata.webhookUrl}`}</p> <p>{`Webhook URL: ${event.metadata.webhookUrl}`}</p>
<p>{`Disabled: ${event.metadata.isDisabled}`}</p> <p>{`Disabled: ${event.metadata.isDisabled}`}</p>
</Td> </Td>
@@ -228,7 +234,7 @@ export const LogsTableRow = ({
return ( return (
<Td> <Td>
<p>{`Environment: ${event.metadata.environment}`}</p> <p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Secret Path: ${event.metadata.secretPath}`}</p> <p>{`Secret path: ${event.metadata.secretPath}`}</p>
<p>{`Webhook URL: ${event.metadata.webhookUrl}`}</p> <p>{`Webhook URL: ${event.metadata.webhookUrl}`}</p>
<p>{`Disabled: ${event.metadata.isDisabled}`}</p> <p>{`Disabled: ${event.metadata.isDisabled}`}</p>
</Td> </Td>
@@ -237,14 +243,42 @@ export const LogsTableRow = ({
return ( return (
<Td> <Td>
<p>{`Environment: ${event.metadata.environment}`}</p> <p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Secret Path: ${event.metadata.secretPath}`}</p> <p>{`Secret path: ${event.metadata.secretPath}`}</p>
<p>{`Webhook URL: ${event.metadata.webhookUrl}`}</p> <p>{`Webhook URL: ${event.metadata.webhookUrl}`}</p>
<p>{`Disabled: ${event.metadata.isDisabled}`}</p> <p>{`Disabled: ${event.metadata.isDisabled}`}</p>
</Td> </Td>
); );
case EventType.GET_SECRET_IMPORTS:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`# Imported paths: ${event.metadata.numberOfImports}`}</p>
</Td>
);
case EventType.CREATE_SECRET_IMPORT:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Imported path: ${event.metadata.importSecretPath}`}</p>
</Td>
);
case EventType.UPDATE_SECRET_IMPORT:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`# Imported path: ${event.metadata.numberOfImports}`}</p>
</Td>
);
case EventType.DELETE_SECRET_IMPORT:
return (
<Td>
<p>{`Environment: ${event.metadata.environment}`}</p>
<p>{`Imported path: ${event.metadata.importSecretPath}`}</p>
</Td>
);
default: default:
return ( return (
<Td>Test</Td> <Td></Td>
); );
} }
} }