From 142fcf0a01aa9333abc5bdc0e3147c819f860e2e Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sat, 5 Aug 2023 16:55:06 +0700 Subject: [PATCH 1/9] Finish preliminary v2 audit logs --- backend/src/controllers/v1/authController.ts | 10 +- .../controllers/v1/membershipOrgController.ts | 4 +- .../src/controllers/v1/passwordController.ts | 6 +- .../src/controllers/v1/workspaceController.ts | 7 +- backend/src/controllers/v2/authController.ts | 6 +- .../controllers/v2/environmentController.ts | 7 +- .../src/controllers/v2/secretsController.ts | 10 +- .../v2/serviceTokenDataController.ts | 18 +- backend/src/controllers/v3/authController.ts | 4 +- .../controllers/v1/organizationsController.ts | 5 +- .../src/ee/controllers/v1/ssoController.ts | 4 +- .../src/ee/controllers/v1/usersController.ts | 2 +- .../ee/controllers/v1/workspaceController.ts | 92 +++++- backend/src/ee/models/auditLog/auditLog.ts | 76 +++++ backend/src/ee/models/auditLog/enums.ts | 20 ++ backend/src/ee/models/auditLog/index.ts | 3 + backend/src/ee/models/auditLog/types.ts | 88 ++++++ backend/src/ee/models/index.ts | 3 +- backend/src/ee/routes/v1/cloudProducts.ts | 3 +- backend/src/ee/routes/v1/organizations.ts | 32 +- backend/src/ee/routes/v1/secret.ts | 5 +- backend/src/ee/routes/v1/secretSnapshot.ts | 4 +- backend/src/ee/routes/v1/sso.ts | 9 +- backend/src/ee/routes/v1/users.ts | 4 +- backend/src/ee/routes/v1/workspace.ts | 53 +++- backend/src/ee/services/EEAuditLogService.ts | 46 +++ backend/src/ee/services/EELicenseService.ts | 17 +- backend/src/ee/services/EESecretService.ts | 4 +- backend/src/ee/services/index.ts | 2 + backend/src/helpers/auth.ts | 117 ++++--- backend/src/helpers/organization.ts | 2 +- backend/src/helpers/secrets.ts | 129 ++++++-- backend/src/helpers/workspace.ts | 3 +- backend/src/interfaces/middleware/index.ts | 32 +- backend/src/middleware/requireAuth.ts | 81 ++--- .../src/middleware/requireWorkspaceAuth.ts | 2 + backend/src/routes/v1/auth.ts | 10 +- backend/src/routes/v1/bot.ts | 6 +- backend/src/routes/v1/integration.ts | 9 +- backend/src/routes/v1/integrationAuth.ts | 27 +- backend/src/routes/v1/inviteOrg.ts | 4 +- backend/src/routes/v1/key.ts | 6 +- backend/src/routes/v1/membership.ts | 10 +- backend/src/routes/v1/membershipOrg.ts | 6 +- backend/src/routes/v1/organization.ts | 26 +- backend/src/routes/v1/password.ts | 10 +- backend/src/routes/v1/secret.ts | 6 +- backend/src/routes/v1/secretImport.ts | 12 +- backend/src/routes/v1/secretScanning.ts | 12 +- backend/src/routes/v1/secretsFolder.ts | 10 +- backend/src/routes/v1/serviceToken.ts | 4 +- backend/src/routes/v1/user.ts | 6 +- backend/src/routes/v1/userAction.ts | 6 +- backend/src/routes/v1/webhook.ts | 12 +- backend/src/routes/v1/workspace.ts | 25 +- backend/src/routes/v2/environment.ts | 10 +- backend/src/routes/v2/organizations.ts | 13 +- backend/src/routes/v2/secret.ts | 19 +- backend/src/routes/v2/secrets.ts | 20 +- backend/src/routes/v2/serviceAccounts.ts | 291 +++++++++--------- backend/src/routes/v2/serviceTokenData.ts | 10 +- backend/src/routes/v2/tags.ts | 8 +- backend/src/routes/v2/users.ts | 25 +- backend/src/routes/v2/workspace.ts | 20 +- backend/src/routes/v3/secrets.ts | 75 ++--- backend/src/routes/v3/workspaces.ts | 8 +- backend/src/types/express/index.d.ts | 4 +- backend/src/utils/posthog.ts | 20 +- backend/src/validation/bot.ts | 83 +---- backend/src/validation/integration.ts | 67 +--- backend/src/validation/integrationAuth.ts | 67 +--- backend/src/validation/membership.ts | 70 ++--- backend/src/validation/membershipOrg.ts | 71 +---- backend/src/validation/organization.ts | 84 +---- backend/src/validation/secrets.ts | 136 +++----- backend/src/validation/serviceAccount.ts | 65 +--- backend/src/validation/serviceTokenData.ts | 82 ++--- backend/src/validation/workspace.ts | 141 ++++----- backend/src/variables/authentication.ts | 9 +- .../src/ee/components/ActivitySideBar.tsx | 1 + frontend/src/ee/components/ActivityTable.tsx | 2 + .../src/hooks/api/auditLogs/constants.tsx | 16 + frontend/src/hooks/api/auditLogs/enums.tsx | 19 ++ frontend/src/hooks/api/auditLogs/index.tsx | 1 + frontend/src/hooks/api/auditLogs/queries.tsx | 53 ++++ frontend/src/hooks/api/auditLogs/types.tsx | 103 +++++++ frontend/src/hooks/api/index.tsx | 1 + frontend/src/hooks/api/subscriptions/types.ts | 1 + frontend/src/hooks/api/workspace/queries.tsx | 4 +- frontend/src/hooks/api/workspace/types.ts | 2 +- frontend/src/layouts/AppLayout/AppLayout.tsx | 12 + .../src/pages/integrations/checkly/create.tsx | 2 +- .../src/pages/project/[id]/logs/index.tsx | 23 ++ .../src/views/Project/LogsPage/LogsPage.tsx | 17 + .../LogsPage/components/LogsFilter.tsx | 147 +++++++++ .../LogsPage/components/LogsSection.tsx | 49 +++ .../Project/LogsPage/components/LogsTable.tsx | 70 +++++ .../LogsPage/components/LogsTableRow.tsx | 114 +++++++ .../Project/LogsPage/components/index.tsx | 1 + frontend/src/views/Project/LogsPage/index.tsx | 1 + 100 files changed, 1885 insertions(+), 1269 deletions(-) create mode 100644 backend/src/ee/models/auditLog/auditLog.ts create mode 100644 backend/src/ee/models/auditLog/enums.ts create mode 100644 backend/src/ee/models/auditLog/index.ts create mode 100644 backend/src/ee/models/auditLog/types.ts create mode 100644 backend/src/ee/services/EEAuditLogService.ts create mode 100644 frontend/src/hooks/api/auditLogs/constants.tsx create mode 100644 frontend/src/hooks/api/auditLogs/enums.tsx create mode 100644 frontend/src/hooks/api/auditLogs/index.tsx create mode 100644 frontend/src/hooks/api/auditLogs/queries.tsx create mode 100644 frontend/src/hooks/api/auditLogs/types.tsx create mode 100644 frontend/src/pages/project/[id]/logs/index.tsx create mode 100644 frontend/src/views/Project/LogsPage/LogsPage.tsx create mode 100644 frontend/src/views/Project/LogsPage/components/LogsFilter.tsx create mode 100644 frontend/src/views/Project/LogsPage/components/LogsSection.tsx create mode 100644 frontend/src/views/Project/LogsPage/components/LogsTable.tsx create mode 100644 frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx create mode 100644 frontend/src/views/Project/LogsPage/components/index.tsx create mode 100644 frontend/src/views/Project/LogsPage/index.tsx diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index c3ab670b6..03a9a7717 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -15,20 +15,20 @@ import { checkUserDevice } from "../../helpers/user"; import { ACTION_LOGIN, ACTION_LOGOUT, - AUTH_MODE_JWT, } from "../../variables"; import { BadRequestError, UnauthorizedRequestError, } from "../../utils/errors"; import { EELogService } from "../../ee/services"; -import { getChannelFromUserAgent } from "../../utils/posthog"; +import { getUserAgentType } from "../../utils/posthog"; import { getHttpsEnabled, getJwtAuthLifetime, getJwtAuthSecret, getJwtRefreshSecret, } from "../../config"; +import { ActorType } from "../../ee/models"; declare module "jsonwebtoken" { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -142,7 +142,7 @@ export const login2 = async (req: Request, res: Response) => { loginAction && await EELogService.createLog({ userId: user._id, actions: [loginAction], - channel: getChannelFromUserAgent(req.headers["user-agent"]), + channel: getUserAgentType(req.headers["user-agent"]), ipAddress: req.realIP, }); @@ -170,7 +170,7 @@ export const login2 = async (req: Request, res: Response) => { * @returns */ export const logout = async (req: Request, res: Response) => { - if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User && req.authData.tokenVersionId) { + if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) { await clearTokens(req.authData.tokenVersionId) } @@ -190,7 +190,7 @@ export const logout = async (req: Request, res: Response) => { logoutAction && await EELogService.createLog({ userId: req.user._id, actions: [logoutAction], - channel: getChannelFromUserAgent(req.headers["user-agent"]), + channel: getUserAgentType(req.headers["user-agent"]), ipAddress: req.realIP, }); diff --git a/backend/src/controllers/v1/membershipOrgController.ts b/backend/src/controllers/v1/membershipOrgController.ts index 02b99537f..0c319fe62 100644 --- a/backend/src/controllers/v1/membershipOrgController.ts +++ b/backend/src/controllers/v1/membershipOrgController.ts @@ -103,14 +103,14 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => { // validate membership const membershipOrg = await MembershipOrg.findOne({ user: req.user._id, - organization: organizationId + organization: new Types.ObjectId(organizationId) }); if (!membershipOrg) { throw new Error("Failed to validate organization membership"); } - const plan = await EELicenseService.getPlan(organizationId); + const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId)); const ssoConfig = await SSOConfig.findOne({ organization: new Types.ObjectId(organizationId) diff --git a/backend/src/controllers/v1/passwordController.ts b/backend/src/controllers/v1/passwordController.ts index 5a2699225..22b90a61a 100644 --- a/backend/src/controllers/v1/passwordController.ts +++ b/backend/src/controllers/v1/passwordController.ts @@ -5,7 +5,7 @@ import * as bigintConversion from "bigint-conversion"; import { BackupPrivateKey, LoginSRPDetail, User } from "../../models"; import { clearTokens, createToken, sendMail } from "../../helpers"; import { TokenService } from "../../services"; -import { AUTH_MODE_JWT, TOKEN_EMAIL_PASSWORD_RESET } from "../../variables"; +import { TOKEN_EMAIL_PASSWORD_RESET } from "../../variables"; import { BadRequestError } from "../../utils/errors"; import { getHttpsEnabled, @@ -13,6 +13,7 @@ import { getJwtSignupSecret, getSiteURL } from "../../config"; +import { ActorType } from "../../ee/models"; /** * Password reset step 1: Send email verification link to email [email] @@ -208,8 +209,7 @@ export const changePassword = async (req: Request, res: Response) => { ); if ( - req.authData.authMode === AUTH_MODE_JWT && - req.authData.authPayload instanceof User && + req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId ) { await clearTokens(req.authData.tokenVersionId); diff --git a/backend/src/controllers/v1/workspaceController.ts b/backend/src/controllers/v1/workspaceController.ts index f26078ace..1425c142c 100644 --- a/backend/src/controllers/v1/workspaceController.ts +++ b/backend/src/controllers/v1/workspaceController.ts @@ -1,3 +1,4 @@ +import { Types } from "mongoose"; import { Request, Response } from "express"; import { IUser, @@ -108,14 +109,14 @@ export const createWorkspace = async (req: Request, res: Response) => { // validate organization membership const membershipOrg = await MembershipOrg.findOne({ user: req.user._id, - organization: organizationId, + organization: new Types.ObjectId(organizationId), }); if (!membershipOrg) { throw new Error("Failed to validate organization membership"); } - const plan = await EELicenseService.getPlan(organizationId); + const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId)); if (plan.workspaceLimit !== null) { // case: limit imposed on number of workspaces allowed @@ -134,7 +135,7 @@ export const createWorkspace = async (req: Request, res: Response) => { // create workspace and add user as member const workspace = await create({ name: workspaceName, - organizationId, + organizationId: new Types.ObjectId(organizationId), }); await addMemberships({ diff --git a/backend/src/controllers/v2/authController.ts b/backend/src/controllers/v2/authController.ts index 282c15288..8f2f4cea0 100644 --- a/backend/src/controllers/v2/authController.ts +++ b/backend/src/controllers/v2/authController.ts @@ -14,7 +14,7 @@ import { ACTION_LOGIN, TOKEN_EMAIL_MFA, } from "../../variables"; -import { getChannelFromUserAgent } from "../../utils/posthog"; // TODO: move this +import { getUserAgentType } from "../../utils/posthog"; // TODO: move this import { getHttpsEnabled, getJwtMfaLifetime, @@ -203,7 +203,7 @@ export const login2 = async (req: Request, res: Response) => { loginAction && await EELogService.createLog({ userId: user._id, actions: [loginAction], - channel: getChannelFromUserAgent(req.headers["user-agent"]), + channel: getUserAgentType(req.headers["user-agent"]), ipAddress: req.ip, }); @@ -336,7 +336,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => { loginAction && await EELogService.createLog({ userId: user._id, actions: [loginAction], - channel: getChannelFromUserAgent(req.headers["user-agent"]), + channel: getUserAgentType(req.headers["user-agent"]), ipAddress: req.realIP, }); diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index 58f1e10f8..e7de3a8cf 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -1,4 +1,5 @@ import { Request, Response } from "express"; +import { Types } from "mongoose"; import { Integration, Membership, @@ -30,7 +31,7 @@ export const createWorkspaceEnvironment = async ( if (!workspace) throw WorkspaceNotFoundError(); - const plan = await EELicenseService.getPlan(workspace.organization.toString()); + const plan = await EELicenseService.getPlan(workspace.organization); if (plan.environmentLimit !== null) { // case: limit imposed on number of environments allowed @@ -58,7 +59,7 @@ export const createWorkspaceEnvironment = async ( }); await workspace.save(); - await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId); + await EELicenseService.refreshPlan(workspace.organization, new Types.ObjectId(workspaceId)); return res.status(200).send({ message: "Successfully created new environment", @@ -215,7 +216,7 @@ export const deleteWorkspaceEnvironment = async ( { $pull: { deniedPermissions: { environmentSlug: environmentSlug } } } ); - await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId); + await EELicenseService.refreshPlan(workspace.organization, new Types.ObjectId(workspaceId)); return res.status(200).send({ message: "Successfully deleted environment", diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index 284c69c09..14efd452a 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -16,7 +16,7 @@ import { EventService } from "../../services"; import { eventPushSecrets } from "../../events"; import { EELogService, EESecretService } from "../../ee/services"; import { SecretService, TelemetryService } from "../../services"; -import { getChannelFromUserAgent } from "../../utils/posthog"; +import { getUserAgentType } from "../../utils/posthog"; import { PERMISSION_WRITE_SECRETS } from "../../variables"; import { userHasNoAbility, @@ -44,7 +44,7 @@ import { getAllImportedSecrets } from "../../services/SecretImportService"; * @param res */ export const batchSecrets = async (req: Request, res: Response) => { - const channel = getChannelFromUserAgent(req.headers["user-agent"]); + const channel = getUserAgentType(req.headers["user-agent"]); const postHogClient = await TelemetryService.getPostHogClient(); const { @@ -416,7 +416,7 @@ export const createSecrets = async (req: Request, res: Response) => { } */ - const channel = getChannelFromUserAgent(req.headers["user-agent"]); + const channel = getUserAgentType(req.headers["user-agent"]); const { workspaceId, environment, @@ -834,7 +834,7 @@ export const getSecrets = async (req: Request, res: Response) => { importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId as string); } - const channel = getChannelFromUserAgent(req.headers["user-agent"]); + const channel = getUserAgentType(req.headers["user-agent"]); const readAction = await EELogService.createAction({ name: ACTION_READ_SECRETS, @@ -1170,7 +1170,7 @@ export const deleteSecrets = async (req: Request, res: Response) => { } */ - const channel = getChannelFromUserAgent(req.headers["user-agent"]); + const channel = getUserAgentType(req.headers["user-agent"]); const toDelete = req.secrets.map((s: any) => s._id); await Secret.deleteMany({ diff --git a/backend/src/controllers/v2/serviceTokenDataController.ts b/backend/src/controllers/v2/serviceTokenDataController.ts index 25176a5ff..10fb50ef1 100644 --- a/backend/src/controllers/v2/serviceTokenDataController.ts +++ b/backend/src/controllers/v2/serviceTokenDataController.ts @@ -1,10 +1,10 @@ import { Request, Response } from "express"; import crypto from "crypto"; import bcrypt from "bcrypt"; -import { ServiceAccount, ServiceTokenData, User } from "../../models"; -import { AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT } from "../../variables"; +import { ServiceTokenData } from "../../models"; import { getSaltRounds } from "../../config"; import { BadRequestError } from "../../utils/errors"; +import { ActorType } from "../../ee/models"; /** * Return service token data associated with service token on request @@ -73,24 +73,16 @@ export const createServiceTokenData = async (req: Request, res: Response) => { expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); } - let user, serviceAccount; - - if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) { + let user; + + if (req.authData.actor.type === ActorType.USER) { user = req.authData.authPayload._id; } - if ( - req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && - req.authData.authPayload instanceof ServiceAccount - ) { - serviceAccount = req.authData.authPayload._id; - } - serviceTokenData = await new ServiceTokenData({ name, workspace: workspaceId, user, - serviceAccount, scopes, lastUsed: new Date(), expiresAt, diff --git a/backend/src/controllers/v3/authController.ts b/backend/src/controllers/v3/authController.ts index 08ef7fa8d..1a91573e4 100644 --- a/backend/src/controllers/v3/authController.ts +++ b/backend/src/controllers/v3/authController.ts @@ -15,7 +15,7 @@ import { ACTION_LOGIN, TOKEN_EMAIL_MFA, } from "../../variables"; -import { getChannelFromUserAgent } from "../../utils/posthog"; // TODO: move this +import { getUserAgentType } from "../../utils/posthog"; // TODO: move this import { getHttpsEnabled, getJwtMfaLifetime, @@ -241,7 +241,7 @@ export const login2 = async (req: Request, res: Response) => { loginAction && await EELogService.createLog({ userId: user._id, actions: [loginAction], - channel: getChannelFromUserAgent(req.headers["user-agent"]), + channel: getUserAgentType(req.headers["user-agent"]), ipAddress: req.realIP, }); diff --git a/backend/src/ee/controllers/v1/organizationsController.ts b/backend/src/ee/controllers/v1/organizationsController.ts index b473bcbd2..b641de985 100644 --- a/backend/src/ee/controllers/v1/organizationsController.ts +++ b/backend/src/ee/controllers/v1/organizationsController.ts @@ -1,3 +1,4 @@ +import { Types } from "mongoose"; import { Request, Response } from "express"; import { getLicenseServerUrl } from "../../../config"; import { licenseServerKeyRequest } from "../../../config/request"; @@ -20,7 +21,7 @@ export const getOrganizationPlan = async (req: Request, res: Response) => { const { organizationId } = req.params; const workspaceId = req.query.workspaceId as string; - const plan = await EELicenseService.getPlan(organizationId, workspaceId); + const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId), new Types.ObjectId(workspaceId)); return res.status(200).send({ plan, @@ -44,7 +45,7 @@ export const startOrganizationTrial = async (req: Request, res: Response) => { } ); - EELicenseService.delPlan(organizationId); + EELicenseService.delPlan(new Types.ObjectId(organizationId)); return res.status(200).send({ url diff --git a/backend/src/ee/controllers/v1/ssoController.ts b/backend/src/ee/controllers/v1/ssoController.ts index 42b4d12be..6f14314b1 100644 --- a/backend/src/ee/controllers/v1/ssoController.ts +++ b/backend/src/ee/controllers/v1/ssoController.ts @@ -59,7 +59,7 @@ export const updateSSOConfig = async (req: Request, res: Response) => { cert, } = req.body; - const plan = await EELicenseService.getPlan(organizationId); + const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId)); if (!plan.samlSSO) return res.status(400).send({ message: "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration." @@ -194,7 +194,7 @@ export const createSSOConfig = async (req: Request, res: Response) => { cert } = req.body; - const plan = await EELicenseService.getPlan(organizationId); + const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId)); if (!plan.samlSSO) return res.status(400).send({ message: "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration." diff --git a/backend/src/ee/controllers/v1/usersController.ts b/backend/src/ee/controllers/v1/usersController.ts index 13e36a883..a492404f5 100644 --- a/backend/src/ee/controllers/v1/usersController.ts +++ b/backend/src/ee/controllers/v1/usersController.ts @@ -8,6 +8,6 @@ import { Request, Response } from "express"; */ export const getMyIp = (req: Request, res: Response) => { return res.status(200).send({ - ip: req.authData.authIP + ip: req.authData.ipAddress }); } \ No newline at end of file diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index d4f097ea2..c865dc8eb 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; import { PipelineStage, Types } from "mongoose"; -import { Secret } from "../../../models"; +import { Secret, Membership, User, ServiceTokenData } from "../../../models"; import { FolderVersion, IPType, @@ -9,7 +9,12 @@ import { SecretSnapshot, SecretVersion, TFolderRootVersionSchema, - TrustedIP + TrustedIP, + AuditLog, + Actor, + ActorType, + UserActor, + ServiceActor } from "../../models"; import { EESecretService } from "../../services"; import { getLatestSecretVersionIds } from "../../helpers/secretVersion"; @@ -593,6 +598,83 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => { }); }; +/** + * Return trusted ips for workspace with id [workspaceId] + * @param req + * @param res + */ +export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { + const { workspaceId } = req.params; + const eventType = req.query.eventType; + const userAgentType = req.query.userAgentType; + const actor = req.query.actor as string | undefined; + + const auditLogs = await AuditLog.find({ + workspace: new Types.ObjectId(workspaceId), + ...(eventType ? { + "event.type": eventType + } : {}), + ...(userAgentType ? { + userAgentType + } : {}), + ...(actor ? { + "actor.type": actor.split("-", 2)[0], + ...(actor.split("-", 2)[0] === ActorType.USER ? { + "actor.metadata.userId": actor.split("-", 2)[1] + } : { + "actor.metadata.serviceId": actor.split("-", 2)[1] + }) + } : {}) + }) + .sort({ createdAt: -1 }); + + return res.status(200).send({ + auditLogs + }); +} + +/** + * Return trusted ips for workspace with id [workspaceId] + * @param req + * @param res + */ +export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Response) => { + const { workspaceId } = req.params; + + const userIds = await Membership.distinct("user", { + workspace: new Types.ObjectId(workspaceId) + }); + const userActors: UserActor[] = (await User.find({ + _id: { + $in: userIds + } + }) + .select("email")) + .map((user) => ({ + type: ActorType.USER, + metadata: { + userId: user._id.toString(), + email: user.email + } + })); + + const serviceActors: ServiceActor[] = (await ServiceTokenData.find({ + workspace: new Types.ObjectId(workspaceId) + }) + .select("name")) + .map((serviceTokenData) => ({ + type: ActorType.SERVICE, + metadata: { + serviceId: serviceTokenData._id.toString(), + name: serviceTokenData.name + } + })); + + return res.status(200).send({ + actors: [...userActors, ...serviceActors] + }); +} + /** * Return trusted ips for workspace with id [workspaceId] * @param req @@ -623,7 +705,7 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => { isActive } = req.body; - const plan = await EELicenseService.getPlan(req.workspace.organization.toString()); + const plan = await EELicenseService.getPlan(req.workspace.organization); if (!plan.ipAllowlisting) return res.status(400).send({ message: "Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range." @@ -663,7 +745,7 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => { comment } = req.body; - const plan = await EELicenseService.getPlan(req.workspace.organization.toString()); + const plan = await EELicenseService.getPlan(req.workspace.organization); if (!plan.ipAllowlisting) return res.status(400).send({ message: "Failed to update IP access range due to plan restriction. Upgrade plan to update IP access range." @@ -721,7 +803,7 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => { export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => { const { workspaceId, trustedIpId } = req.params; - const plan = await EELicenseService.getPlan(req.workspace.organization.toString()); + const plan = await EELicenseService.getPlan(req.workspace.organization); if (!plan.ipAllowlisting) return res.status(400).send({ message: "Failed to delete IP access range due to plan restriction. Upgrade plan to delete IP access range." diff --git a/backend/src/ee/models/auditLog/auditLog.ts b/backend/src/ee/models/auditLog/auditLog.ts new file mode 100644 index 000000000..a38527e85 --- /dev/null +++ b/backend/src/ee/models/auditLog/auditLog.ts @@ -0,0 +1,76 @@ +import { Schema, Types, model } from "mongoose"; +import { + ActorType, + EventType, + UserAgentType +} from "./enums"; +import { + Actor, + Event +} from "./types"; + +export interface IAuditLog { + actor: Actor; + organization: Types.ObjectId; + workspace: Types.ObjectId; + ipAddress: string; + event: Event; + userAgent: string; + userAgentType: UserAgentType; + expiresAt: Date; +} + +const auditLogSchema = new Schema( + { + actor: { + type: { + type: String, + enum: ActorType, + required: true + }, + metadata: { + type: Schema.Types.Mixed + } + }, + organization: { + type: Schema.Types.ObjectId, + required: false + }, + workspace: { + type: Schema.Types.ObjectId, + required: false + }, + ipAddress: { + type: String, + required: true + }, + event: { + type: { + type: String, + enum: EventType, + required: true + }, + metadata: { + type: Schema.Types.Mixed + } + }, + userAgent: { + type: String, + required: true + }, + userAgentType: { + type: String, + enum: UserAgentType, + required: true + }, + expiresAt: { + type: Date, + expires: 0 + } + }, + { + timestamps: true + } +); + +export const AuditLog = model("AuditLog", auditLogSchema); diff --git a/backend/src/ee/models/auditLog/enums.ts b/backend/src/ee/models/auditLog/enums.ts new file mode 100644 index 000000000..ce415e8c9 --- /dev/null +++ b/backend/src/ee/models/auditLog/enums.ts @@ -0,0 +1,20 @@ +export enum ActorType { + USER = "user", + SERVICE = "service" +} + +export enum UserAgentType { + WEB = "web", + CLI = "cli", + K8_OPERATOR = "k8-operator", + OTHER = "other" +} + +export enum EventType { + GET_SECRETS = "get-secrets", + GET_SECRET = "get-secret", + REVEAL_SECRET = "reveal-secret", + CREATE_SECRET = "create-secret", + UPDATE_SECRET = "update-secret", + DELETE_SECRET = "delete-secret" +} \ No newline at end of file diff --git a/backend/src/ee/models/auditLog/index.ts b/backend/src/ee/models/auditLog/index.ts new file mode 100644 index 000000000..37b86b5d1 --- /dev/null +++ b/backend/src/ee/models/auditLog/index.ts @@ -0,0 +1,3 @@ +export * from "./auditLog"; +export * from "./enums"; +export * from "./types"; \ No newline at end of file diff --git a/backend/src/ee/models/auditLog/types.ts b/backend/src/ee/models/auditLog/types.ts new file mode 100644 index 000000000..848fb88db --- /dev/null +++ b/backend/src/ee/models/auditLog/types.ts @@ -0,0 +1,88 @@ +import { + ActorType, + EventType +} from "./enums"; + +interface UserActorMetadata { + userId: string; + email: string; +} + +interface ServiceActorMetadata { + serviceId: string; + name: string; +} + +export interface UserActor { + type: ActorType.USER; + metadata: UserActorMetadata; +} + +export interface ServiceActor { + type: ActorType.SERVICE; + metadata: ServiceActorMetadata; +} + +export type Actor = + | UserActor + | ServiceActor; + +interface GetSecretsEvent { + type: EventType.GET_SECRETS; + metadata: { + environment: string; + secretPath: string; + numberOfSecrets: number; + }; +} + +interface GetSecretEvent { + type: EventType.GET_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + }; +} + +interface CreateSecretEvent { + type: EventType.CREATE_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + } +} + +interface UpdateSecretEvent { + type: EventType.UPDATE_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + } +} + +interface DeleteSecretEvent { + type: EventType.DELETE_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + } +} + +export type Event = + | GetSecretsEvent + | GetSecretEvent + | CreateSecretEvent + | UpdateSecretEvent + | DeleteSecretEvent; \ No newline at end of file diff --git a/backend/src/ee/models/index.ts b/backend/src/ee/models/index.ts index 1def9073d..420e51d3a 100644 --- a/backend/src/ee/models/index.ts +++ b/backend/src/ee/models/index.ts @@ -4,4 +4,5 @@ export * from "./folderVersion"; export * from "./log"; export * from "./action"; export * from "./ssoConfig"; -export * from "./trustedIp"; \ No newline at end of file +export * from "./trustedIp"; +export * from "./auditLog"; diff --git a/backend/src/ee/routes/v1/cloudProducts.ts b/backend/src/ee/routes/v1/cloudProducts.ts index a9be34747..81256f378 100644 --- a/backend/src/ee/routes/v1/cloudProducts.ts +++ b/backend/src/ee/routes/v1/cloudProducts.ts @@ -6,11 +6,12 @@ import { } from "../../../middleware"; import { query } from "express-validator"; import { cloudProductsController } from "../../controllers/v1"; +import { AuthMode } from "../../../variables"; router.get( "/", requireAuth({ - acceptedAuthModes: ["jwt", "apiKey"], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), query("billing-cycle").exists().isIn(["monthly", "yearly"]), validateRequest, diff --git a/backend/src/ee/routes/v1/organizations.ts b/backend/src/ee/routes/v1/organizations.ts index 5c308466d..d41d232da 100644 --- a/backend/src/ee/routes/v1/organizations.ts +++ b/backend/src/ee/routes/v1/organizations.ts @@ -8,13 +8,13 @@ import { import { body, param, query } from "express-validator"; import { organizationsController } from "../../controllers/v1"; import { - ACCEPTED, ADMIN, MEMBER, OWNER, + ACCEPTED, ADMIN, MEMBER, OWNER, AuthMode } from "../../../variables"; router.get( "/:organizationId/plans/table", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -29,7 +29,7 @@ router.get( router.get( "/:organizationId/plan", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -44,7 +44,7 @@ router.get( router.post( "/:organizationId/session/trial", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -59,7 +59,7 @@ router.post( router.get( "/:organizationId/plan/billing", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -74,7 +74,7 @@ router.get( router.get( "/:organizationId/plan/table", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -89,7 +89,7 @@ router.get( router.get( "/:organizationId/billing-details", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -103,7 +103,7 @@ router.get( router.patch( "/:organizationId/billing-details", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -119,7 +119,7 @@ router.patch( router.get( "/:organizationId/billing-details/payment-methods", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -133,7 +133,7 @@ router.get( router.post( "/:organizationId/billing-details/payment-methods", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -149,7 +149,7 @@ router.post( router.delete( "/:organizationId/billing-details/payment-methods/:pmtMethodId", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -164,7 +164,7 @@ router.delete( router.get( "/:organizationId/billing-details/tax-ids", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -178,7 +178,7 @@ router.get( router.post( "/:organizationId/billing-details/tax-ids", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -194,7 +194,7 @@ router.post( router.delete( "/:organizationId/billing-details/tax-ids/:taxId", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -209,7 +209,7 @@ router.delete( router.get( "/:organizationId/invoices", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -223,7 +223,7 @@ router.get( router.get( "/:organizationId/licenses", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN], diff --git a/backend/src/ee/routes/v1/secret.ts b/backend/src/ee/routes/v1/secret.ts index 7be6f311a..376922e74 100644 --- a/backend/src/ee/routes/v1/secret.ts +++ b/backend/src/ee/routes/v1/secret.ts @@ -12,12 +12,13 @@ import { MEMBER, PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS, + AuthMode } from "../../../variables"; router.get( "/:secretId/secret-versions", requireAuth({ - acceptedAuthModes: ["jwt", "apiKey"], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireSecretAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -33,7 +34,7 @@ router.get( router.post( "/:secretId/secret-versions/rollback", requireAuth({ - acceptedAuthModes: ["jwt", "apiKey"], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireSecretAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/ee/routes/v1/secretSnapshot.ts b/backend/src/ee/routes/v1/secretSnapshot.ts index fe0c2690c..c6201cc12 100644 --- a/backend/src/ee/routes/v1/secretSnapshot.ts +++ b/backend/src/ee/routes/v1/secretSnapshot.ts @@ -8,13 +8,13 @@ import { validateRequest, } from "../../../middleware"; import { param } from "express-validator"; -import { ADMIN, MEMBER } from "../../../variables"; +import { ADMIN, MEMBER, AuthMode } from "../../../variables"; import { secretSnapshotController } from "../../controllers/v1"; router.get( "/:secretSnapshotId", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireSecretSnapshotAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/ee/routes/v1/sso.ts b/backend/src/ee/routes/v1/sso.ts index f76f19e7d..22ecee9f0 100644 --- a/backend/src/ee/routes/v1/sso.ts +++ b/backend/src/ee/routes/v1/sso.ts @@ -15,7 +15,8 @@ import { authLimiter } from "../../../helpers/rateLimiter"; import { ACCEPTED, ADMIN, - OWNER + OWNER, + AuthMode } from "../../../variables"; router.get( @@ -90,7 +91,7 @@ router.post("/saml2/:ssoIdentifier", router.get( "/config", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN], @@ -105,7 +106,7 @@ router.get( router.post( "/config", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN], @@ -125,7 +126,7 @@ router.post( router.patch( "/config", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN], diff --git a/backend/src/ee/routes/v1/users.ts b/backend/src/ee/routes/v1/users.ts index 14dcaa49c..d5015401e 100644 --- a/backend/src/ee/routes/v1/users.ts +++ b/backend/src/ee/routes/v1/users.ts @@ -3,13 +3,13 @@ const router = express.Router(); import { requireAuth } from "../../../middleware"; -import { AUTH_MODE_API_KEY, AUTH_MODE_JWT } from "../../../variables"; +import { AuthMode } from "../../../variables"; import { usersController } from "../../controllers/v1"; router.get( "/me/ip", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), usersController.getMyIp ); diff --git a/backend/src/ee/routes/v1/workspace.ts b/backend/src/ee/routes/v1/workspace.ts index 49b13e6a5..407f5d3f8 100644 --- a/backend/src/ee/routes/v1/workspace.ts +++ b/backend/src/ee/routes/v1/workspace.ts @@ -8,16 +8,16 @@ import { import { body, param, query } from "express-validator"; import { ADMIN, - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - MEMBER + MEMBER, + AuthMode } from "../../../variables"; import { workspaceController } from "../../controllers/v1"; +import { EventType, UserAgentType } from "../../models"; router.get( "/:workspaceId/secret-snapshots", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -35,7 +35,7 @@ router.get( router.get( "/:workspaceId/secret-snapshots/count", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -51,7 +51,7 @@ router.get( router.post( "/:workspaceId/secret-snapshots/rollback", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -68,7 +68,7 @@ router.post( router.get( "/:workspaceId/logs", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -84,11 +84,42 @@ router.get( workspaceController.getWorkspaceLogs ); +router.get( + "/:workspaceId/audit-logs", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], + }), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + locationWorkspaceId: "params", + }), + param("workspaceId").exists().trim(), + query("eventType").isString().isIn(Object.values(EventType)).optional({ nullable: true }), + query("userAgentType").isString().isIn(Object.values(UserAgentType)).optional({ nullable: true }), + query("actor").isString().optional({ nullable: true }), + validateRequest, + workspaceController.getWorkspaceAuditLogs +); + +router.get( + "/:workspaceId/audit-logs/filters/actors", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], + }), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER], + locationWorkspaceId: "params", + }), + param("workspaceId").exists().trim(), + validateRequest, + workspaceController.getWorkspaceAuditLogActorFilterOpts +); + router.get( "/:workspaceId/trusted-ips", param("workspaceId").exists().isString().trim(), requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -105,7 +136,7 @@ router.post( body("isActive").exists().isBoolean(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], @@ -122,7 +153,7 @@ router.patch( body("comment").default("").isString().trim(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], @@ -137,7 +168,7 @@ router.delete( param("trustedIpId").exists().isString().trim(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], diff --git a/backend/src/ee/services/EEAuditLogService.ts b/backend/src/ee/services/EEAuditLogService.ts new file mode 100644 index 000000000..02ea99225 --- /dev/null +++ b/backend/src/ee/services/EEAuditLogService.ts @@ -0,0 +1,46 @@ +import { Types } from "mongoose"; +import { AuditLog, Event } from "../models"; +import { AuthData } from "../../interfaces/middleware"; +import EELicenseService from "./EELicenseService"; +import { Workspace } from "../../models"; +import { OrganizationNotFoundError } from "../../utils/errors"; + +interface EventScope { + workspaceId?: Types.ObjectId; + organizationId?: Types.ObjectId; +} + +type ValidEventScope = + | Required> + | Required> + | Required + +export default class EEAuditLogService { + static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope) { + + const MS_IN_DAY = 24 * 60 * 60 * 1000; + + const organizationId = ("organizationId" in eventScope) + ? eventScope.organizationId + : (await Workspace.findById(eventScope.workspaceId).select("organization").lean())?.organization; + + if (!organizationId) throw OrganizationNotFoundError({ + message: "createAuditLog: Failed to create audit log due to missing organizationId" + }); + + const ttl = (await EELicenseService.getPlan(organizationId)).auditLogsRetentionDays * MS_IN_DAY; + + const auditLog = await new AuditLog({ + actor: authData.actor, + organization: organizationId, + workspace: ("workspaceId" in eventScope) ? eventScope.workspaceId : undefined, + ipAddress: authData.ipAddress, + event, + userAgent: authData.userAgent, + userAgentType: authData.userAgentType, + expiresAt: new Date(Date.now() + ttl) + }).save(); + + return auditLog; + } +} \ No newline at end of file diff --git a/backend/src/ee/services/EELicenseService.ts b/backend/src/ee/services/EELicenseService.ts index 12e3e496a..de8e4369d 100644 --- a/backend/src/ee/services/EELicenseService.ts +++ b/backend/src/ee/services/EELicenseService.ts @@ -1,3 +1,4 @@ +import { Types } from "mongoose"; import * as Sentry from "@sentry/node"; import NodeCache from "node-cache"; import { @@ -31,6 +32,7 @@ interface FeatureSet { customRateLimits: boolean; customAlerts: boolean; auditLogs: boolean; + auditLogsRetentionDays: number; samlSSO: boolean; status: "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | null; trial_end: number | null; @@ -66,6 +68,7 @@ class EELicenseService { customRateLimits: true, customAlerts: true, auditLogs: false, + auditLogsRetentionDays: 0, samlSSO: false, status: null, trial_end: null, @@ -81,10 +84,10 @@ class EELicenseService { }); } - public async getPlan(organizationId: string, workspaceId?: string): Promise { + public async getPlan(organizationId: Types.ObjectId, workspaceId?: Types.ObjectId): Promise { try { if (this.instanceType === "cloud") { - const cachedPlan = this.localFeatureSet.get(`${organizationId}-${workspaceId ?? ""}`); + const cachedPlan = this.localFeatureSet.get(`${organizationId.toString()}-${workspaceId?.toString() ?? ""}`); if (cachedPlan) { return cachedPlan; } @@ -101,7 +104,7 @@ class EELicenseService { const { data: { currentPlan } } = await licenseServerKeyRequest.get(url); // cache fetched plan for organization - this.localFeatureSet.set(`${organizationId}-${workspaceId ?? ""}`, currentPlan); + this.localFeatureSet.set(`${organizationId.toString()}-${workspaceId?.toString() ?? ""}`, currentPlan); return currentPlan; } @@ -112,16 +115,16 @@ class EELicenseService { return this.globalFeatureSet; } - public async refreshPlan(organizationId: string, workspaceId?: string) { + public async refreshPlan(organizationId: Types.ObjectId, workspaceId?: Types.ObjectId) { if (this.instanceType === "cloud") { - this.localFeatureSet.del(`${organizationId}-${workspaceId ?? ""}`); + this.localFeatureSet.del(`${organizationId.toString()}-${workspaceId?.toString() ?? ""}`); await this.getPlan(organizationId, workspaceId); } } - public async delPlan(organizationId: string) { + public async delPlan(organizationId: Types.ObjectId) { if (this.instanceType === "cloud") { - this.localFeatureSet.del(`${organizationId}-`); + this.localFeatureSet.del(`${organizationId.toString()}-`); } } diff --git a/backend/src/ee/services/EESecretService.ts b/backend/src/ee/services/EESecretService.ts index 5a1c4d3cb..1e065b6cd 100644 --- a/backend/src/ee/services/EESecretService.ts +++ b/backend/src/ee/services/EESecretService.ts @@ -10,7 +10,7 @@ import EELicenseService from "./EELicenseService"; /** * Class to handle Enterprise Edition secret actions */ -class EESecretService { +export default class EESecretService { /** * Save a secret snapshot that is a copy of the current state of secrets in workspace with id * [workspaceId] under a new snapshot with incremented version under the @@ -71,5 +71,3 @@ class EESecretService { }); } } - -export default EESecretService; diff --git a/backend/src/ee/services/index.ts b/backend/src/ee/services/index.ts index afc3fb80e..ba25df7fa 100644 --- a/backend/src/ee/services/index.ts +++ b/backend/src/ee/services/index.ts @@ -1,9 +1,11 @@ import EELicenseService from "./EELicenseService"; import EESecretService from "./EESecretService"; import EELogService from "./EELogService"; +import EEAuditLogService from "./EEAuditLogService"; export { EELicenseService, EESecretService, EELogService, + EEAuditLogService } \ No newline at end of file diff --git a/backend/src/helpers/auth.ts b/backend/src/helpers/auth.ts index eb6eeb4d2..50b68e62c 100644 --- a/backend/src/helpers/auth.ts +++ b/backend/src/helpers/auth.ts @@ -1,3 +1,4 @@ +import { Request } from "express"; import { Types } from "mongoose"; import jwt from "jsonwebtoken"; import bcrypt from "bcrypt"; @@ -5,7 +6,6 @@ import { APIKeyData, ITokenVersion, IUser, - ServiceAccount, ServiceTokenData, TokenVersion, User, @@ -14,7 +14,6 @@ import { APIKeyDataNotFoundError, AccountNotFoundError, BadRequestError, - ServiceAccountNotFoundError, ServiceTokenDataNotFoundError, UnauthorizedRequestError, } from "../utils/errors"; @@ -26,11 +25,15 @@ import { getJwtRefreshSecret, } from "../config"; import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, + AuthMode } from "../variables"; +import { + UserAuthData, + ServiceTokenAuthData +} from "../interfaces/middleware"; + +import { ActorType } from "../ee/models"; +import { getUserAgentType } from "../utils/posthog"; /** * @@ -42,7 +45,7 @@ export const validateAuthMode = ({ acceptedAuthModes, }: { headers: { [key: string]: string | string[] | undefined }, - acceptedAuthModes: string[] + acceptedAuthModes: AuthMode[] }) => { const apiKey = headers["x-api-key"]; const authHeader = headers["authorization"]; @@ -55,7 +58,7 @@ export const validateAuthMode = ({ if (typeof apiKey === "string") { // case: treat request authentication type as via X-API-KEY (i.e. API Key) - authMode = AUTH_MODE_API_KEY; + authMode = AuthMode.API_KEY; authTokenValue = apiKey; } @@ -71,13 +74,10 @@ export const validateAuthMode = ({ switch (tokenValue.split(".", 1)[0]) { case "st": - authMode = AUTH_MODE_SERVICE_TOKEN; - break; - case "sa": - authMode = AUTH_MODE_SERVICE_ACCOUNT; + authMode = AuthMode.SERVICE_TOKEN; break; default: - authMode = AUTH_MODE_JWT; + authMode = AuthMode.JWT; } authTokenValue = tokenValue; @@ -100,10 +100,12 @@ export const validateAuthMode = ({ * @returns {User} user - user corresponding to JWT token */ export const getAuthUserPayload = async ({ + req, authTokenValue, }: { + req: Request, authTokenValue: string; -}) => { +}): Promise => { const decodedToken = ( jwt.verify(authTokenValue, await getJwtAuthSecret()) ); @@ -130,11 +132,25 @@ export const getAuthUserPayload = async ({ if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({ message: "Failed to validate access token", }); - - return ({ - user, - tokenVersionId: tokenVersion._id, - }); + + return { + actor: { + type: ActorType.USER, + metadata: { + userId: user._id.toString(), + email: user.email + } + }, + authPayload: user, + ipAddress: req.realIP, + userAgent: req.headers["user-agent"] ?? "", + userAgentType: getUserAgentType(req.headers["user-agent"]) + } + + // return ({ + // user, + // tokenVersionId: tokenVersion._id, // what to do with this? // move this out + // }); } /** @@ -144,10 +160,12 @@ export const getAuthUserPayload = async ({ * @returns {ServiceTokenData} serviceTokenData - service token data */ export const getAuthSTDPayload = async ({ + req, authTokenValue, }: { + req: Request, authTokenValue: string; -}) => { +}): Promise => { const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3); const serviceTokenData = await ServiceTokenData @@ -180,36 +198,21 @@ export const getAuthSTDPayload = async ({ if (!serviceTokenDataToReturn) throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" }); - return serviceTokenDataToReturn; -} - -/** - * Return service account access key payload - * @param {Object} obj - * @param {String} obj.authTokenValue - service account access token value - * @returns {ServiceAccount} serviceAccount - */ -export const getAuthSAAKPayload = async ({ - authTokenValue, -}: { - authTokenValue: string; -}) => { - const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3); - - const serviceAccount = await ServiceAccount.findById( - Buffer.from(TOKEN_IDENTIFIER, "base64").toString("hex") - ).select("+secretHash"); - - if (!serviceAccount) { - throw ServiceAccountNotFoundError({ message: "Failed to find service account" }); + return { + actor: { + type: ActorType.SERVICE, + metadata: { + serviceId: serviceTokenDataToReturn._id.toString(), + name: serviceTokenDataToReturn.name + } + }, + authPayload: serviceTokenDataToReturn, + ipAddress: req.realIP, + userAgent: req.headers["user-agent"] ?? "", + userAgentType: getUserAgentType(req.headers["user-agent"]) } - const result = await bcrypt.compare(TOKEN_SECRET, serviceAccount.secretHash); - if (!result) throw UnauthorizedRequestError({ - message: "Failed to authenticate service account access key", - }); - - return serviceAccount; + // return serviceTokenDataToReturn; } /** @@ -219,10 +222,12 @@ export const getAuthSAAKPayload = async ({ * @returns {APIKeyData} apiKeyData - API key data */ export const getAuthAPIKeyPayload = async ({ + req, authTokenValue, }: { + req: Request, authTokenValue: string; -}) => { +}): Promise => { const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3); let apiKeyData = await APIKeyData @@ -264,7 +269,19 @@ export const getAuthAPIKeyPayload = async ({ }); } - return user; + return { + actor: { + type: ActorType.USER, + metadata: { + userId: user._id.toString(), + email: user.email + } + }, + authPayload: user, + ipAddress: req.realIP, + userAgent: req.headers["user-agent"] ?? "", + userAgentType: getUserAgentType(req.headers["user-agent"]) + } } /** diff --git a/backend/src/helpers/organization.ts b/backend/src/helpers/organization.ts index 3123e1c16..1ba5c7abc 100644 --- a/backend/src/helpers/organization.ts +++ b/backend/src/helpers/organization.ts @@ -115,5 +115,5 @@ export const updateSubscriptionOrgQuantity = async ({ ); } - await EELicenseService.refreshPlan(organizationId); + await EELicenseService.refreshPlan(new Types.ObjectId(organizationId)); }; \ No newline at end of file diff --git a/backend/src/helpers/secrets.ts b/backend/src/helpers/secrets.ts index 17e5c93ce..0d5562b4e 100644 --- a/backend/src/helpers/secrets.ts +++ b/backend/src/helpers/secrets.ts @@ -11,9 +11,9 @@ import { IServiceTokenData, Secret, SecretBlindIndexData, - ServiceTokenData + ServiceTokenData, } from "../models"; -import { SecretVersion } from "../ee/models"; +import { SecretVersion, EventType } from "../ee/models"; import { BadRequestError, InternalServerError, @@ -40,7 +40,7 @@ import { } from "../utils/crypto"; import { TelemetryService } from "../services"; import { client, getEncryptionKey, getRootEncryptionKey } from "../config"; -import { EELogService, EESecretService } from "../ee/services"; +import { EELogService, EESecretService, EEAuditLogService } from "../ee/services"; import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/auth"; import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService"; import picomatch from "picomatch"; @@ -433,10 +433,27 @@ export const createSecretHelper = async ({ ...getAuthDataPayloadIdObj(authData), workspaceId, actions: [action], - channel: authData.authChannel, - ipAddress: authData.authIP + channel: authData.userAgentType, + ipAddress: authData.ipAddress })); + await EEAuditLogService.createAuditLog( + authData, + { + type: EventType.CREATE_SECRET, + metadata: { + environment, + secretPath, + secretId: secret._id.toString(), + secretKey: secretName, + secretVersion: secret.version + } + }, + { + workspaceId + } + ); + // (EE) take a secret snapshot await EESecretService.takeSecretSnapshot({ workspaceId, @@ -457,8 +474,8 @@ export const createSecretHelper = async ({ environment, workspaceId, folderId, - channel: authData.authChannel, - userAgent: authData.authUserAgent + channel: authData.userAgentType, + userAgent: authData.userAgent } }); } @@ -528,9 +545,24 @@ export const getSecretsHelper = async ({ ...getAuthDataPayloadIdObj(authData), workspaceId, actions: [action], - channel: authData.authChannel, - ipAddress: authData.authIP + channel: authData.userAgentType, + ipAddress: authData.ipAddress })); + + await EEAuditLogService.createAuditLog( + authData, + { + type: EventType.GET_SECRETS, + metadata: { + environment, + secretPath, + numberOfSecrets: secrets.length + } + }, + { + workspaceId + } + ); const postHogClient = await TelemetryService.getPostHogClient(); @@ -545,8 +577,8 @@ export const getSecretsHelper = async ({ environment, workspaceId, folderId, - channel: authData.authChannel, - userAgent: authData.authUserAgent + channel: authData.userAgentType, + userAgent: authData.userAgent } }); } @@ -622,10 +654,27 @@ export const getSecretHelper = async ({ ...getAuthDataPayloadIdObj(authData), workspaceId, actions: [action], - channel: authData.authChannel, - ipAddress: authData.authIP + channel: authData.userAgentType, + ipAddress: authData.ipAddress })); + await EEAuditLogService.createAuditLog( + authData, + { + type: EventType.GET_SECRET, + metadata: { + environment, + secretPath, + secretId: secret._id.toString(), + secretKey: secretName, + secretVersion: secret.version + } + }, + { + workspaceId + } + ); + const postHogClient = await TelemetryService.getPostHogClient(); if (postHogClient) { @@ -639,8 +688,8 @@ export const getSecretHelper = async ({ environment, workspaceId, folderId, - channel: authData.authChannel, - userAgent: authData.authUserAgent + channel: authData.userAgentType, + userAgent: authData.userAgent } }); } @@ -771,9 +820,26 @@ export const updateSecretHelper = async ({ ...getAuthDataPayloadIdObj(authData), workspaceId, actions: [action], - channel: authData.authChannel, - ipAddress: authData.authIP + channel: authData.userAgentType, + ipAddress: authData.ipAddress })); + + await EEAuditLogService.createAuditLog( + authData, + { + type: EventType.UPDATE_SECRET, + metadata: { + environment, + secretPath, + secretId: secret._id.toString(), + secretKey: secretName, + secretVersion: secret.version + } + }, + { + workspaceId + } + ); // (EE) take a secret snapshot await EESecretService.takeSecretSnapshot({ @@ -795,8 +861,8 @@ export const updateSecretHelper = async ({ environment, workspaceId, folderId, - channel: authData.authChannel, - userAgent: authData.authUserAgent + channel: authData.userAgentType, + userAgent: authData.userAgent } }); } @@ -894,10 +960,27 @@ export const deleteSecretHelper = async ({ ...getAuthDataPayloadIdObj(authData), workspaceId, actions: [action], - channel: authData.authChannel, - ipAddress: authData.authIP + channel: authData.userAgentType, + ipAddress: authData.ipAddress })); + await EEAuditLogService.createAuditLog( + authData, + { + type: EventType.DELETE_SECRET, + metadata: { + environment, + secretPath, + secretId: secret._id.toString(), + secretKey: secretName, + secretVersion: secret.version + } + }, + { + workspaceId + } + ); + // (EE) take a secret snapshot await EESecretService.takeSecretSnapshot({ workspaceId, @@ -918,8 +1001,8 @@ export const deleteSecretHelper = async ({ environment, workspaceId, folderId, - channel: authData.authChannel, - userAgent: authData.authUserAgent + channel: authData.userAgentType, + userAgent: authData.userAgent } }); } diff --git a/backend/src/helpers/workspace.ts b/backend/src/helpers/workspace.ts index ef38e4fc1..b65a4543b 100644 --- a/backend/src/helpers/workspace.ts +++ b/backend/src/helpers/workspace.ts @@ -1,3 +1,4 @@ +import { Types } from "mongoose"; import { Bot, Key, @@ -25,7 +26,7 @@ export const createWorkspace = async ({ organizationId, }: { name: string; - organizationId: string; + organizationId: Types.ObjectId; }) => { // create workspace const workspace = await new Workspace({ diff --git a/backend/src/interfaces/middleware/index.ts b/backend/src/interfaces/middleware/index.ts index 3fba92348..bb2435ecf 100644 --- a/backend/src/interfaces/middleware/index.ts +++ b/backend/src/interfaces/middleware/index.ts @@ -1,15 +1,31 @@ import { Types } from "mongoose"; import { - IServiceAccount, IServiceTokenData, IUser, } from "../../models"; +import { + UserActor, + ServiceActor, + UserAgentType +} from "../../ee/models"; -export interface AuthData { - authMode: string; - authPayload: IUser | IServiceAccount | IServiceTokenData; - authChannel: string; - authIP: string; - authUserAgent: string; +interface BaseAuthData { + ipAddress: string; + userAgent: string; + userAgentType: UserAgentType; tokenVersionId?: Types.ObjectId; -} \ No newline at end of file +} + +export interface UserAuthData extends BaseAuthData { + actor: UserActor; + authPayload: IUser; +} + +export interface ServiceTokenAuthData extends BaseAuthData { + actor: ServiceActor; + authPayload: IServiceTokenData; +} + +export type AuthData = + | UserAuthData + | ServiceTokenAuthData; \ No newline at end of file diff --git a/backend/src/middleware/requireAuth.ts b/backend/src/middleware/requireAuth.ts index 6c20be25a..e256f6665 100644 --- a/backend/src/middleware/requireAuth.ts +++ b/backend/src/middleware/requireAuth.ts @@ -1,25 +1,13 @@ import jwt from "jsonwebtoken"; -import { Types } from "mongoose"; import { NextFunction, Request, Response } from "express"; import { getAuthAPIKeyPayload, - getAuthSAAKPayload, getAuthSTDPayload, getAuthUserPayload, validateAuthMode, } from "../helpers/auth"; -import { - IServiceAccount, - IServiceTokenData, - IUser, -} from "../models"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, -} from "../variables"; -import { getChannelFromUserAgent } from "../utils/posthog"; +import { AuthMode } from "../variables"; +import { AuthData } from "../interfaces/middleware"; declare module "jsonwebtoken" { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -38,9 +26,9 @@ declare module "jsonwebtoken" { * @returns */ const requireAuth = ({ - acceptedAuthModes = [AUTH_MODE_JWT], + acceptedAuthModes = [AuthMode.JWT], }: { - acceptedAuthModes: string[]; + acceptedAuthModes: AuthMode[]; }) => { return async (req: Request, res: Response, next: NextFunction) => { @@ -50,55 +38,36 @@ const requireAuth = ({ headers: req.headers, acceptedAuthModes, }); - - let authPayload: IUser | IServiceAccount | IServiceTokenData; - let authUserPayload: { - user: IUser; - tokenVersionId: Types.ObjectId; - }; + + let authData: AuthData; + switch (authMode) { - case AUTH_MODE_SERVICE_ACCOUNT: - authPayload = await getAuthSAAKPayload({ + case AuthMode.SERVICE_TOKEN: + authData = await getAuthSTDPayload({ + req, authTokenValue, }); - req.serviceAccount = authPayload; + req.serviceTokenData = authData.authPayload; break; - case AUTH_MODE_SERVICE_TOKEN: - authPayload = await getAuthSTDPayload({ - authTokenValue, + case AuthMode.API_KEY: + authData = await getAuthAPIKeyPayload({ + req, + authTokenValue }); - req.serviceTokenData = authPayload; + req.user = authData.authPayload; break; - case AUTH_MODE_API_KEY: - authPayload = await getAuthAPIKeyPayload({ - authTokenValue, + case AuthMode.JWT: + authData = await getAuthUserPayload({ + req, + authTokenValue }); - req.user = authPayload; - break; - default: - authUserPayload = await getAuthUserPayload({ - authTokenValue, - }); - authPayload = authUserPayload.user; - req.user = authUserPayload.user; - req.tokenVersionId = authUserPayload.tokenVersionId; + // authPayload = authUserPayload.user; + req.user = authData.authPayload; + // req.tokenVersionId = authUserPayload.tokenVersionId; // TODO break; } - - req.requestData = { - ...req.params, - ...req.query, - ...req.body, - } - - req.authData = { - authMode, - authPayload, // User, ServiceAccount, ServiceTokenData - authChannel: getChannelFromUserAgent(req.headers["user-agent"]), - authIP: req.realIP, - authUserAgent: req.headers["user-agent"] ?? "other", - tokenVersionId: req.tokenVersionId, - } + + req.authData = authData; return next(); } diff --git a/backend/src/middleware/requireWorkspaceAuth.ts b/backend/src/middleware/requireWorkspaceAuth.ts index f6f7405a9..e5e1e447d 100644 --- a/backend/src/middleware/requireWorkspaceAuth.ts +++ b/backend/src/middleware/requireWorkspaceAuth.ts @@ -32,6 +32,8 @@ const requireWorkspaceAuth = ({ const workspaceId = req[locationWorkspaceId]?.workspaceId; const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined; + console.log("workspaceId: ", workspaceId); + // validate clients const { membership, workspace } = await validateClientForWorkspace({ authData: req.authData, diff --git a/backend/src/routes/v1/auth.ts b/backend/src/routes/v1/auth.ts index ae85aa36c..ce21f5136 100644 --- a/backend/src/routes/v1/auth.ts +++ b/backend/src/routes/v1/auth.ts @@ -4,7 +4,7 @@ import { body } from "express-validator"; import { requireAuth, validateRequest } from "../../middleware"; import { authController } from "../../controllers/v1"; import { authLimiter } from "../../helpers/rateLimiter"; -import { AUTH_MODE_JWT } from "../../variables"; +import { AuthMode } from "../../variables"; router.post("/token", validateRequest, authController.getNewToken); @@ -30,7 +30,7 @@ router.post( "/logout", authLimiter, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), authController.logout ); @@ -38,7 +38,7 @@ router.post( router.post( "/checkAuth", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), authController.checkAuth ); @@ -53,9 +53,9 @@ router.delete( "/sessions", authLimiter, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), authController.revokeAllSessions ); -export default router; +export default router; \ No newline at end of file diff --git a/backend/src/routes/v1/bot.ts b/backend/src/routes/v1/bot.ts index 0eafecad0..536e0a3bd 100644 --- a/backend/src/routes/v1/bot.ts +++ b/backend/src/routes/v1/bot.ts @@ -8,12 +8,12 @@ import { validateRequest, } from "../../middleware"; import { botController } from "../../controllers/v1"; -import { ADMIN, AUTH_MODE_JWT, MEMBER } from "../../variables"; +import { ADMIN, MEMBER, AuthMode } from "../../variables"; router.get( "/:workspaceId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -27,7 +27,7 @@ router.get( router.patch( "/:botId/active", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireBotAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v1/integration.ts b/backend/src/routes/v1/integration.ts index 1820f4bb8..0bd1dfbc6 100644 --- a/backend/src/routes/v1/integration.ts +++ b/backend/src/routes/v1/integration.ts @@ -8,9 +8,8 @@ import { } from "../../middleware"; import { ADMIN, - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, MEMBER, + AuthMode } from "../../variables"; import { body, param } from "express-validator"; import { integrationController } from "../../controllers/v1"; @@ -18,7 +17,7 @@ import { integrationController } from "../../controllers/v1"; router.post( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -44,7 +43,7 @@ router.post( router.patch( "/:integrationId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT] }), requireIntegrationAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -64,7 +63,7 @@ router.patch( router.delete( "/:integrationId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT] }), requireIntegrationAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v1/integrationAuth.ts b/backend/src/routes/v1/integrationAuth.ts index 4fdc290e7..daf880c60 100644 --- a/backend/src/routes/v1/integrationAuth.ts +++ b/backend/src/routes/v1/integrationAuth.ts @@ -9,16 +9,15 @@ import { } from "../../middleware"; import { ADMIN, - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, MEMBER, + AuthMode } from "../../variables"; import { integrationAuthController } from "../../controllers/v1"; router.get( "/integration-options", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), integrationAuthController.getIntegrationOptions ); @@ -26,7 +25,7 @@ router.get( router.get( "/:integrationAuthId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -39,7 +38,7 @@ router.get( router.post( "/oauth-token", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -62,7 +61,7 @@ router.post( body("integration").exists().trim().notEmpty(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -74,7 +73,7 @@ router.post( router.get( "/:integrationAuthId/apps", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -89,7 +88,7 @@ router.get( router.get( "/:integrationAuthId/teams", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -102,7 +101,7 @@ router.get( router.get( "/:integrationAuthId/vercel/branches", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -117,7 +116,7 @@ router.get( router.get( "/:integrationAuthId/railway/environments", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -131,7 +130,7 @@ router.get( router.get( "/:integrationAuthId/railway/services", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -145,7 +144,7 @@ router.get( router.get( "/:integrationAuthId/bitbucket/workspaces", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -158,7 +157,7 @@ router.get( router.get( "/:integrationAuthId/northflank/secret-groups", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -172,7 +171,7 @@ router.get( router.delete( "/:integrationAuthId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v1/inviteOrg.ts b/backend/src/routes/v1/inviteOrg.ts index edcb34c87..089c0e53a 100644 --- a/backend/src/routes/v1/inviteOrg.ts +++ b/backend/src/routes/v1/inviteOrg.ts @@ -3,12 +3,12 @@ const router = express.Router(); import { body } from "express-validator"; import { requireAuth, validateRequest } from "../../middleware"; import { membershipOrgController } from "../../controllers/v1"; -import { AUTH_MODE_JWT } from "../../variables"; +import { AuthMode } from "../../variables"; router.post( "/signup", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), body("inviteeEmail").exists().trim().notEmpty().isEmail(), body("organizationId").exists().trim().notEmpty(), diff --git a/backend/src/routes/v1/key.ts b/backend/src/routes/v1/key.ts index fbd6e3eca..daa840ee7 100644 --- a/backend/src/routes/v1/key.ts +++ b/backend/src/routes/v1/key.ts @@ -6,13 +6,13 @@ import { validateRequest, } from "../../middleware"; import { body, param } from "express-validator"; -import { ADMIN, AUTH_MODE_JWT, MEMBER } from "../../variables"; +import { ADMIN, MEMBER, AuthMode } from "../../variables"; import { keyController } from "../../controllers/v1"; router.post( "/:workspaceId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -27,7 +27,7 @@ router.post( router.get( "/:workspaceId/latest", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v1/membership.ts b/backend/src/routes/v1/membership.ts index 428213768..be495b62c 100644 --- a/backend/src/routes/v1/membership.ts +++ b/backend/src/routes/v1/membership.ts @@ -4,14 +4,14 @@ import { body, param } from "express-validator"; import { requireAuth, validateRequest } from "../../middleware"; import { membershipController } from "../../controllers/v1"; import { membershipController as EEMembershipControllers } from "../../ee/controllers/v1"; -import { AUTH_MODE_JWT } from "../../variables"; +import { AuthMode } from "../../variables"; // note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId) router.get( // used for old CLI (deprecate) "/:workspaceId/connect", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), param("workspaceId").exists().trim(), validateRequest, @@ -21,7 +21,7 @@ router.get( // used for old CLI (deprecate) router.delete( "/:membershipId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), param("membershipId").exists().trim(), validateRequest, @@ -31,7 +31,7 @@ router.delete( router.post( "/:membershipId/change-role", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), body("role").exists().trim(), validateRequest, @@ -41,7 +41,7 @@ router.post( router.post( "/:membershipId/deny-permissions", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), param("membershipId").isMongoId().exists().trim(), body("permissions").isArray().exists(), diff --git a/backend/src/routes/v1/membershipOrg.ts b/backend/src/routes/v1/membershipOrg.ts index 6b3c7d2e8..92472eb03 100644 --- a/backend/src/routes/v1/membershipOrg.ts +++ b/backend/src/routes/v1/membershipOrg.ts @@ -3,13 +3,13 @@ const router = express.Router(); import { param } from "express-validator"; import { requireAuth, validateRequest } from "../../middleware"; import { membershipOrgController } from "../../controllers/v1"; -import { AUTH_MODE_JWT } from "../../variables"; +import { AuthMode } from "../../variables"; router.post( // TODO "/membershipOrg/:membershipOrgId/change-role", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), param("membershipOrgId"), validateRequest, @@ -19,7 +19,7 @@ router.post( router.delete( "/:membershipOrgId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), param("membershipOrgId").exists().trim(), validateRequest, diff --git a/backend/src/routes/v1/organization.ts b/backend/src/routes/v1/organization.ts index 7cfd0e3fa..fdad1f9fa 100644 --- a/backend/src/routes/v1/organization.ts +++ b/backend/src/routes/v1/organization.ts @@ -9,16 +9,16 @@ import { import { ACCEPTED, ADMIN, - AUTH_MODE_JWT, MEMBER, OWNER, + AuthMode } from "../../variables"; import { organizationController } from "../../controllers/v1"; router.get( // deprecated (moved to api/v2/users/me/organizations) "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), organizationController.getOrganizations ); @@ -26,7 +26,7 @@ router.get( // deprecated (moved to api/v2/users/me/organizations) router.post( // not used on frontend "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), body("organizationName").exists().trim().notEmpty(), validateRequest, @@ -36,7 +36,7 @@ router.post( // not used on frontend router.get( "/:organizationId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -50,7 +50,7 @@ router.get( router.get( // deprecated (moved to api/v2/organizations/:organizationId/memberships) "/:organizationId/users", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -64,7 +64,7 @@ router.get( // deprecated (moved to api/v2/organizations/:organizationId/members router.get( "/:organizationId/my-workspaces", // deprecated (moved to api/v2/organizations/:organizationId/workspaces) requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -78,7 +78,7 @@ router.get( router.patch( "/:organizationId/name", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -93,7 +93,7 @@ router.patch( router.get( "/:organizationId/incidentContactOrg", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -107,7 +107,7 @@ router.get( router.post( "/:organizationId/incidentContactOrg", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -122,7 +122,7 @@ router.post( router.delete( "/:organizationId/incidentContactOrg", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -137,7 +137,7 @@ router.delete( router.post( "/:organizationId/customer-portal-session", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -151,7 +151,7 @@ router.post( router.get( "/:organizationId/subscriptions", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -165,7 +165,7 @@ router.get( router.get( "/:organizationId/workspace-memberships", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT] }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], diff --git a/backend/src/routes/v1/password.ts b/backend/src/routes/v1/password.ts index 7268b1a3c..3bccc0934 100644 --- a/backend/src/routes/v1/password.ts +++ b/backend/src/routes/v1/password.ts @@ -4,14 +4,12 @@ import { body } from "express-validator"; import { requireAuth, requireSignupAuth, validateRequest } from "../../middleware"; import { passwordController } from "../../controllers/v1"; import { passwordLimiter } from "../../helpers/rateLimiter"; -import { - AUTH_MODE_JWT, -} from "../../variables"; +import { AuthMode } from "../../variables"; router.post( "/srp1", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), body("clientPublicKey").exists().isString().trim().notEmpty(), validateRequest, @@ -22,7 +20,7 @@ router.post( "/change-password", passwordLimiter, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), body("clientProof").exists().trim().notEmpty(), body("protectedKey").exists().isString().trim().notEmpty(), @@ -65,7 +63,7 @@ router.post( "/backup-private-key", passwordLimiter, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), body("clientProof").exists().isString().trim().notEmpty(), body("encryptedPrivateKey").exists().isString().trim().notEmpty(), // (backup) private key encrypted under a strong key diff --git a/backend/src/routes/v1/secret.ts b/backend/src/routes/v1/secret.ts index 89ed3c975..6cebd4db8 100644 --- a/backend/src/routes/v1/secret.ts +++ b/backend/src/routes/v1/secret.ts @@ -10,8 +10,8 @@ import { body, param, query } from "express-validator"; import { secretController } from "../../controllers/v1"; import { ADMIN, - AUTH_MODE_JWT, MEMBER, + AuthMode } from "../../variables"; // note to devs: these endpoints will be deprecated in favor of v2 @@ -19,7 +19,7 @@ import { router.post( "/:workspaceId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -37,7 +37,7 @@ router.post( router.get( "/:workspaceId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v1/secretImport.ts b/backend/src/routes/v1/secretImport.ts index 655fbd933..2e7e22bc3 100644 --- a/backend/src/routes/v1/secretImport.ts +++ b/backend/src/routes/v1/secretImport.ts @@ -3,12 +3,12 @@ const router = express.Router(); import { body, param, query } from "express-validator"; import { secretImportController } from "../../controllers/v1"; import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware"; -import { ADMIN, AUTH_MODE_JWT, MEMBER } from "../../variables"; +import { ADMIN, MEMBER, AuthMode } from "../../variables"; router.post( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT] }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -27,7 +27,7 @@ router.post( router.put( "/:id", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT] }), param("id").exists().isString().trim(), body("secretImports").exists().isArray(), @@ -40,7 +40,7 @@ router.put( router.delete( "/:id", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT] }), param("id").exists().isString().trim(), body("secretImportPath").isString().exists().trim(), @@ -52,7 +52,7 @@ router.delete( router.get( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT] }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -68,7 +68,7 @@ router.get( router.get( "/secrets", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT] }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v1/secretScanning.ts b/backend/src/routes/v1/secretScanning.ts index fa162ce68..135556b1e 100644 --- a/backend/src/routes/v1/secretScanning.ts +++ b/backend/src/routes/v1/secretScanning.ts @@ -7,12 +7,12 @@ import { } from "../../middleware"; import { body, param } from "express-validator"; import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../controllers/v1/secretScanningController"; -import { ACCEPTED, ADMIN, MEMBER, OWNER } from "../../variables"; +import { ACCEPTED, ADMIN, MEMBER, OWNER, AuthMode } from "../../variables"; router.post( "/create-installation-session/organization/:organizationId", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), param("organizationId").exists().trim(), requireOrganizationAuth({ @@ -26,7 +26,7 @@ router.post( router.post( "/link-installation", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), body("installationId").exists().trim(), body("sessionId").exists().trim(), @@ -37,7 +37,7 @@ router.post( router.get( "/installation-status/organization/:organizationId", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), param("organizationId").exists().trim(), requireOrganizationAuth({ @@ -51,7 +51,7 @@ router.get( router.get( "/organization/:organizationId/risks", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), param("organizationId").exists().trim(), requireOrganizationAuth({ @@ -65,7 +65,7 @@ router.get( router.post( "/organization/:organizationId/risks/:riskId/status", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), param("organizationId").exists().trim(), param("riskId").exists().trim(), diff --git a/backend/src/routes/v1/secretsFolder.ts b/backend/src/routes/v1/secretsFolder.ts index 83517f1ab..6a89e4b97 100644 --- a/backend/src/routes/v1/secretsFolder.ts +++ b/backend/src/routes/v1/secretsFolder.ts @@ -12,12 +12,12 @@ import { getFolders, updateFolderById, } from "../../controllers/v1/secretsFolderController"; -import { ADMIN, MEMBER } from "../../variables"; +import { ADMIN, MEMBER, AuthMode } from "../../variables"; router.post( "/", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -34,7 +34,7 @@ router.post( router.patch( "/:folderId", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), body("workspaceId").exists(), body("environment").exists(), @@ -46,7 +46,7 @@ router.patch( router.delete( "/:folderId", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), body("workspaceId").exists(), body("environment").exists(), @@ -58,7 +58,7 @@ router.delete( router.get( "/", requireAuth({ - acceptedAuthModes: ["jwt"], + acceptedAuthModes: [AuthMode.JWT], }), query("workspaceId").exists().isString().trim(), query("environment").exists().isString().trim(), diff --git a/backend/src/routes/v1/serviceToken.ts b/backend/src/routes/v1/serviceToken.ts index b3f3abb70..a3974e217 100644 --- a/backend/src/routes/v1/serviceToken.ts +++ b/backend/src/routes/v1/serviceToken.ts @@ -9,8 +9,8 @@ import { import { body } from "express-validator"; import { ADMIN, - AUTH_MODE_JWT, MEMBER, + AuthMode } from "../../variables"; import { serviceTokenController } from "../../controllers/v1"; @@ -25,7 +25,7 @@ router.get( router.post( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v1/user.ts b/backend/src/routes/v1/user.ts index d499a2377..73012c1ea 100644 --- a/backend/src/routes/v1/user.ts +++ b/backend/src/routes/v1/user.ts @@ -2,14 +2,12 @@ import express from "express"; const router = express.Router(); import { requireAuth } from "../../middleware"; import { userController } from "../../controllers/v1"; -import { - AUTH_MODE_JWT, -} from "../../variables"; +import { AuthMode } from "../../variables"; router.get( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), userController.getUser ); diff --git a/backend/src/routes/v1/userAction.ts b/backend/src/routes/v1/userAction.ts index 29cc811bd..042f73c10 100644 --- a/backend/src/routes/v1/userAction.ts +++ b/backend/src/routes/v1/userAction.ts @@ -3,13 +3,13 @@ const router = express.Router(); import { requireAuth, validateRequest } from "../../middleware"; import { body, query } from "express-validator"; import { userActionController } from "../../controllers/v1"; -import { AUTH_MODE_JWT } from "../../variables"; +import { AuthMode } from "../../variables"; // note: [userAction] will be deprecated in /v2 in favor of [action] router.post( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), body("action"), validateRequest, @@ -19,7 +19,7 @@ router.post( router.get( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), query("action"), validateRequest, diff --git a/backend/src/routes/v1/webhook.ts b/backend/src/routes/v1/webhook.ts index 11507e68b..55c471ea3 100644 --- a/backend/src/routes/v1/webhook.ts +++ b/backend/src/routes/v1/webhook.ts @@ -2,13 +2,13 @@ import express from "express"; const router = express.Router(); import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware"; import { body, param, query } from "express-validator"; -import { ADMIN, AUTH_MODE_JWT, MEMBER } from "../../variables"; +import { ADMIN, MEMBER, AuthMode } from "../../variables"; import { webhookController } from "../../controllers/v1"; router.post( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -27,7 +27,7 @@ router.post( router.patch( "/:webhookId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT], }), param("webhookId").exists().isString().trim(), body("isDisabled").default(false).isBoolean(), @@ -38,7 +38,7 @@ router.patch( router.post( "/:webhookId/test", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT], }), param("webhookId").exists().isString().trim(), validateRequest, @@ -48,7 +48,7 @@ router.post( router.delete( "/:webhookId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT], }), param("webhookId").exists().isString().trim(), validateRequest, @@ -58,7 +58,7 @@ router.delete( router.get( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v1/workspace.ts b/backend/src/routes/v1/workspace.ts index 665473b1e..2fd178f52 100644 --- a/backend/src/routes/v1/workspace.ts +++ b/backend/src/routes/v1/workspace.ts @@ -8,16 +8,15 @@ import { } from "../../middleware"; import { ADMIN, - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, MEMBER, + AuthMode } from "../../variables"; import { membershipController, workspaceController } from "../../controllers/v1"; router.get( "/:workspaceId/keys", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -31,7 +30,7 @@ router.get( router.get( "/:workspaceId/users", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -45,7 +44,7 @@ router.get( router.get( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), workspaceController.getWorkspaces ); @@ -53,7 +52,7 @@ router.get( router.get( "/:workspaceId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -67,7 +66,7 @@ router.get( router.post( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), body("workspaceName").exists().trim().notEmpty(), body("organizationId").exists().trim().notEmpty(), @@ -78,7 +77,7 @@ router.post( router.delete( "/:workspaceId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], @@ -92,7 +91,7 @@ router.delete( router.post( "/:workspaceId/name", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -107,7 +106,7 @@ router.post( router.post( "/:workspaceId/invite-signup", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -122,7 +121,7 @@ router.post( router.get( "/:workspaceId/integrations", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -136,7 +135,7 @@ router.get( router.get( "/:workspaceId/authorizations", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -150,7 +149,7 @@ router.get( router.get( "/:workspaceId/service-tokens", // deprecate requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v2/environment.ts b/backend/src/routes/v2/environment.ts index f9943f33d..f383aa2fd 100644 --- a/backend/src/routes/v2/environment.ts +++ b/backend/src/routes/v2/environment.ts @@ -9,14 +9,14 @@ import { } from "../../middleware"; import { ADMIN, - AUTH_MODE_JWT, MEMBER, + AuthMode } from "../../variables"; router.post( "/:workspaceId/environments", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -32,7 +32,7 @@ router.post( router.put( "/:workspaceId/environments", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -49,7 +49,7 @@ router.put( router.delete( "/:workspaceId/environments", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], @@ -64,7 +64,7 @@ router.delete( router.get( "/:workspaceId/environments", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [MEMBER, ADMIN], diff --git a/backend/src/routes/v2/organizations.ts b/backend/src/routes/v2/organizations.ts index 46223cf93..6196796cd 100644 --- a/backend/src/routes/v2/organizations.ts +++ b/backend/src/routes/v2/organizations.ts @@ -10,10 +10,9 @@ import { body, param } from "express-validator"; import { ACCEPTED, ADMIN, - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, MEMBER, OWNER, + AuthMode } from "../../variables"; import { organizationsController } from "../../controllers/v2"; @@ -24,7 +23,7 @@ router.get( param("organizationId").exists().trim(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN, MEMBER], @@ -40,7 +39,7 @@ router.patch( body("role").exists().isString().trim().isIn([OWNER, ADMIN, MEMBER]), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN], @@ -59,7 +58,7 @@ router.delete( param("membershipId").exists().trim(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN], @@ -77,7 +76,7 @@ router.get( param("organizationId").exists().trim(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN], @@ -91,7 +90,7 @@ router.get( param("organizationId").exists().trim(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT] }), requireOrganizationAuth({ acceptedRoles: [OWNER, ADMIN], diff --git a/backend/src/routes/v2/secret.ts b/backend/src/routes/v2/secret.ts index e577d2a47..9b7526a99 100644 --- a/backend/src/routes/v2/secret.ts +++ b/backend/src/routes/v2/secret.ts @@ -8,9 +8,8 @@ import { import { body, param, query } from "express-validator"; import { ADMIN, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_TOKEN, MEMBER, + AuthMode, PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS, } from "../../variables"; @@ -24,7 +23,7 @@ const router = express.Router(); router.post( "/batch-create/workspace/:workspaceId/environment/:environment", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -41,7 +40,7 @@ router.post( router.post( "/workspace/:workspaceId/environment/:environment", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -60,7 +59,7 @@ router.get( param("workspaceId").exists().trim(), query("environment").exists(), requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_TOKEN], + acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -74,7 +73,7 @@ router.get( router.get( "/:secretId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_TOKEN], + acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN], }), requireSecretAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -87,7 +86,7 @@ router.get( router.delete( "/batch/workspace/:workspaceId/environment/:environmentName", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), param("workspaceId").exists().isMongoId().trim(), param("environmentName").exists().trim(), @@ -103,7 +102,7 @@ router.delete( router.delete( "/:secretId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireSecretAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -117,7 +116,7 @@ router.delete( router.patch( "/batch-modify/workspace/:workspaceId/environment/:environmentName", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), body("secrets").exists().isArray().custom((secrets: ModifySecretRequestBody[]) => secrets.length > 0), param("workspaceId").exists().isMongoId().trim(), @@ -133,7 +132,7 @@ router.patch( router.patch( "/workspace/:workspaceId/environment/:environmentName", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), body("secret").isObject(), param("workspaceId").exists().isMongoId().trim(), diff --git a/backend/src/routes/v2/secrets.ts b/backend/src/routes/v2/secrets.ts index 59665fd03..e347e11c3 100644 --- a/backend/src/routes/v2/secrets.ts +++ b/backend/src/routes/v2/secrets.ts @@ -12,11 +12,8 @@ import { body, query } from "express-validator"; import { secretsController } from "../../controllers/v2"; import { ADMIN, - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, MEMBER, + AuthMode, PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS, SECRET_PERSONAL, @@ -27,7 +24,7 @@ import { BatchSecretRequest } from "../../types/secret"; router.post( "/batch", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN] + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN] }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -109,7 +106,7 @@ router.post( }), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN] + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN] }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -130,12 +127,7 @@ router.get( query("include_imports").optional().default(false).isBoolean(), validateRequest, requireAuth({ - acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT - ] + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN] }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -172,7 +164,7 @@ router.patch( }), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN] + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN] }), requireSecretsAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -201,7 +193,7 @@ router.delete( .isEmpty(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN] + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN] }), requireSecretsAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v2/serviceAccounts.ts b/backend/src/routes/v2/serviceAccounts.ts index 244739e72..daf048a23 100644 --- a/backend/src/routes/v2/serviceAccounts.ts +++ b/backend/src/routes/v2/serviceAccounts.ts @@ -1,159 +1,158 @@ import express from "express"; const router = express.Router(); -import { - requireAuth, - requireOrganizationAuth, - requireServiceAccountAuth, - requireServiceAccountWorkspacePermissionAuth, - requireWorkspaceAuth, - validateRequest, -} from "../../middleware"; -import { body, param, query } from "express-validator"; -import { - ACCEPTED, - ADMIN, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - MEMBER, - OWNER, -} from "../../variables"; -import { serviceAccountsController } from "../../controllers/v2"; +// 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( // 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.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.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.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.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.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.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.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 -); +// 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; \ No newline at end of file diff --git a/backend/src/routes/v2/serviceTokenData.ts b/backend/src/routes/v2/serviceTokenData.ts index 84f443deb..aafd1cd47 100644 --- a/backend/src/routes/v2/serviceTokenData.ts +++ b/backend/src/routes/v2/serviceTokenData.ts @@ -9,10 +9,8 @@ import { import { body, param } from "express-validator"; import { ADMIN, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, MEMBER, + AuthMode, PERMISSION_WRITE_SECRETS } from "../../variables"; import { serviceTokenDataController } from "../../controllers/v2"; @@ -20,7 +18,7 @@ import { serviceTokenDataController } from "../../controllers/v2"; router.get( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_SERVICE_TOKEN] + acceptedAuthModes: [AuthMode.SERVICE_TOKEN] }), serviceTokenDataController.getServiceTokenData ); @@ -28,7 +26,7 @@ router.get( router.post( "/", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT] + acceptedAuthModes: [AuthMode.JWT] }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -63,7 +61,7 @@ router.post( router.delete( "/:serviceTokenDataId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT] + acceptedAuthModes: [AuthMode.JWT] }), requireServiceTokenDataAuth({ acceptedRoles: [ADMIN, MEMBER] diff --git a/backend/src/routes/v2/tags.ts b/backend/src/routes/v2/tags.ts index 8974bd9fd..af82db6ac 100644 --- a/backend/src/routes/v2/tags.ts +++ b/backend/src/routes/v2/tags.ts @@ -9,14 +9,14 @@ import { } from "../../middleware"; import { ADMIN, - AUTH_MODE_JWT, MEMBER, + AuthMode } from "../../variables"; router.get( "/:workspaceId/tags", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [MEMBER, ADMIN], @@ -30,7 +30,7 @@ router.get( router.delete( "/tags/:tagId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), param("tagId").exists().trim(), validateRequest, @@ -40,7 +40,7 @@ router.delete( router.post( "/:workspaceId/tags", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [MEMBER, ADMIN], diff --git a/backend/src/routes/v2/users.ts b/backend/src/routes/v2/users.ts index 334ef523b..378627296 100644 --- a/backend/src/routes/v2/users.ts +++ b/backend/src/routes/v2/users.ts @@ -6,10 +6,7 @@ import { } from "../../middleware"; import { body, param } from "express-validator"; import { usersController } from "../../controllers/v2"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, -} from "../../variables"; +import { AuthMode } from "../../variables"; import { AuthProvider } from "../../models"; @@ -17,7 +14,7 @@ import { router.get( "/me", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), usersController.getMe ); @@ -25,7 +22,7 @@ router.get( router.patch( "/me/mfa", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), body("isMfaEnabled").exists().isBoolean(), validateRequest, @@ -35,7 +32,7 @@ router.patch( router.patch( "/me/name", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), body("firstName").exists().isString(), body("lastName").isString(), @@ -46,7 +43,7 @@ router.patch( router.patch( "/me/auth-provider", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), body("authProvider").exists().isString().isIn([ AuthProvider.EMAIL, @@ -60,7 +57,7 @@ router.patch( router.get( "/me/organizations", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), usersController.getMyOrganizations ); @@ -68,7 +65,7 @@ router.get( router.get( "/me/api-keys", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT] }), usersController.getMyAPIKeys ); @@ -76,7 +73,7 @@ router.get( router.post( "/me/api-keys", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT] }), body("name").exists().isString().trim(), body("expiresIn").isNumeric(), @@ -87,7 +84,7 @@ router.post( router.delete( "/me/api-keys/:apiKeyDataId", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT] }), param("apiKeyDataId").exists().trim(), validateRequest, @@ -97,7 +94,7 @@ router.delete( router.get( "/me/sessions", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT] }), usersController.getMySessions ); @@ -105,7 +102,7 @@ router.get( router.delete( "/me/sessions", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT] }), usersController.deleteMySessions ); diff --git a/backend/src/routes/v2/workspace.ts b/backend/src/routes/v2/workspace.ts index c36a0b7f8..a93bad48e 100644 --- a/backend/src/routes/v2/workspace.ts +++ b/backend/src/routes/v2/workspace.ts @@ -9,17 +9,15 @@ import { } from "../../middleware"; import { ADMIN, - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_TOKEN, MEMBER, + AuthMode } from "../../variables"; import { workspaceController } from "../../controllers/v2"; router.post( "/:workspaceId/secrets", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -37,7 +35,7 @@ router.post( router.get( "/:workspaceId/secrets", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_TOKEN], + acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -53,7 +51,7 @@ router.get( router.get( "/:workspaceId/encrypted-key", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -67,7 +65,7 @@ router.get( router.get( "/:workspaceId/service-token-data", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -83,7 +81,7 @@ router.get( // new - TODO: rewire dashboard to this route param("workspaceId").exists().trim(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], @@ -99,7 +97,7 @@ router.patch( // TODO - rewire dashboard to this route body("role").exists().isString().trim().isIn([ADMIN, MEMBER]), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], @@ -118,7 +116,7 @@ router.delete( // TODO - rewire dashboard to this route param("membershipId").exists().trim(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY], + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], @@ -134,7 +132,7 @@ router.delete( // TODO - rewire dashboard to this route router.patch( "/:workspaceId/auto-capitalization", requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT] }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER], diff --git a/backend/src/routes/v3/secrets.ts b/backend/src/routes/v3/secrets.ts index 6d3b4911d..11500cb97 100644 --- a/backend/src/routes/v3/secrets.ts +++ b/backend/src/routes/v3/secrets.ts @@ -5,11 +5,8 @@ import { body, param, query } from "express-validator"; import { secretsController } from "../../controllers/v3"; import { ADMIN, - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, MEMBER, + AuthMode, PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS, SECRET_PERSONAL, @@ -25,10 +22,9 @@ router.get( validateRequest, requireAuth({ acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT + AuthMode.JWT, + AuthMode.API_KEY, + AuthMode.SERVICE_TOKEN ] }), secretsController.getSecretsRaw @@ -44,10 +40,9 @@ router.get( validateRequest, requireAuth({ acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT + AuthMode.JWT, + AuthMode.API_KEY, + AuthMode.SERVICE_TOKEN ] }), requireWorkspaceAuth({ @@ -73,10 +68,9 @@ router.post( validateRequest, requireAuth({ acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT + AuthMode.JWT, + AuthMode.API_KEY, + AuthMode.SERVICE_TOKEN ] }), requireWorkspaceAuth({ @@ -102,10 +96,9 @@ router.patch( validateRequest, requireAuth({ acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT + AuthMode.JWT, + AuthMode.API_KEY, + AuthMode.SERVICE_TOKEN ] }), requireWorkspaceAuth({ @@ -130,10 +123,9 @@ router.delete( validateRequest, requireAuth({ acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT + AuthMode.JWT, + AuthMode.API_KEY, + AuthMode.SERVICE_TOKEN ] }), requireWorkspaceAuth({ @@ -156,10 +148,9 @@ router.get( validateRequest, requireAuth({ acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT + AuthMode.JWT, + AuthMode.API_KEY, + AuthMode.SERVICE_TOKEN ] }), requireWorkspaceAuth({ @@ -192,10 +183,9 @@ router.post( validateRequest, requireAuth({ acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT + AuthMode.JWT, + AuthMode.API_KEY, + AuthMode.SERVICE_TOKEN ] }), requireWorkspaceAuth({ @@ -220,10 +210,9 @@ router.get( validateRequest, requireAuth({ acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT + AuthMode.JWT, + AuthMode.API_KEY, + AuthMode.SERVICE_TOKEN ] }), requireWorkspaceAuth({ @@ -250,10 +239,9 @@ router.patch( validateRequest, requireAuth({ acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT + AuthMode.JWT, + AuthMode.API_KEY, + AuthMode.SERVICE_TOKEN ] }), requireWorkspaceAuth({ @@ -278,10 +266,9 @@ router.delete( validateRequest, requireAuth({ acceptedAuthModes: [ - AUTH_MODE_JWT, - AUTH_MODE_API_KEY, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_SERVICE_ACCOUNT + AuthMode.JWT, + AuthMode.API_KEY, + AuthMode.SERVICE_TOKEN ] }), requireWorkspaceAuth({ diff --git a/backend/src/routes/v3/workspaces.ts b/backend/src/routes/v3/workspaces.ts index 7aa909693..38695e8b9 100644 --- a/backend/src/routes/v3/workspaces.ts +++ b/backend/src/routes/v3/workspaces.ts @@ -8,7 +8,7 @@ import { import { workspacesController } from "../../controllers/v3"; import { ADMIN, - AUTH_MODE_JWT, + AuthMode } from "../../variables"; import { body, param } from "express-validator"; @@ -19,7 +19,7 @@ router.get( param("workspaceId").exists().isString().trim(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], @@ -33,7 +33,7 @@ router.get( // allow admins to get all workspace secrets (part of blind indices param("workspaceId").exists().isString().trim(), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], @@ -65,7 +65,7 @@ router.post( // allow admins to name all workspace secrets (part of blind indice .withMessage("secretId must be a string"), validateRequest, requireAuth({ - acceptedAuthModes: [AUTH_MODE_JWT], + acceptedAuthModes: [AuthMode.JWT], }), requireWorkspaceAuth({ acceptedRoles: [ADMIN], diff --git a/backend/src/types/express/index.d.ts b/backend/src/types/express/index.d.ts index c7713a327..d3a5fbc23 100644 --- a/backend/src/types/express/index.d.ts +++ b/backend/src/types/express/index.d.ts @@ -1,8 +1,6 @@ import { Types } from "mongoose"; - - import { - AuthData, + AuthData } from "../../interfaces/middleware"; declare module "express" { diff --git a/backend/src/utils/posthog.ts b/backend/src/utils/posthog.ts index 06c404888..0de0fa120 100644 --- a/backend/src/utils/posthog.ts +++ b/backend/src/utils/posthog.ts @@ -1,15 +1,15 @@ -const CLI_USER_AGENT_NAME = "cli" -const K8_OPERATOR_AGENT_NAME = "k8-operator" -export const getChannelFromUserAgent = function (userAgent: string | undefined) { +import { UserAgentType } from "../ee/models" + +export const getUserAgentType = function (userAgent: string | undefined) { if (userAgent == undefined) { - return "other" - } else if (userAgent == CLI_USER_AGENT_NAME) { - return "cli" - } else if (userAgent == K8_OPERATOR_AGENT_NAME) { - return "k8-operator" + return UserAgentType.OTHER; + } else if (userAgent == UserAgentType.CLI) { + return UserAgentType.CLI; + } else if (userAgent == UserAgentType.K8_OPERATOR) { + return UserAgentType.K8_OPERATOR; } else if (userAgent.toLowerCase().includes("mozilla")) { - return "web" + return UserAgentType.WEB; } else { - return "other" + return UserAgentType.OTHER; } } \ No newline at end of file diff --git a/backend/src/validation/bot.ts b/backend/src/validation/bot.ts index 7e19ca770..2bb6ec6dd 100644 --- a/backend/src/validation/bot.ts +++ b/backend/src/validation/bot.ts @@ -1,25 +1,15 @@ import { Types } from "mongoose"; import { Bot, - IServiceAccount, - IServiceTokenData, IUser, - ServiceAccount, - ServiceTokenData, - User, } from "../models"; -import { validateServiceAccountClientForWorkspace } from "./serviceAccount"; import { validateUserClientForWorkspace } from "./user"; import { BotNotFoundError, UnauthorizedRequestError, } from "../utils/errors"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, -} from "../variables"; +import { AuthData } from "../interfaces/middleware"; +import { ActorType } from "../ee/models"; /** * Validate authenticated clients for bot with id [botId] based @@ -34,65 +24,24 @@ export const validateClientForBot = async ({ botId, acceptedRoles, }: { - authData: { - authMode: string; - authPayload: IUser | IServiceAccount | IServiceTokenData; - }; + authData: AuthData; botId: Types.ObjectId; acceptedRoles: Array<"admin" | "member">; }) => { const bot = await Bot.findById(botId); - if (!bot) throw BotNotFoundError(); - - if ( - authData.authMode === AUTH_MODE_JWT && - authData.authPayload instanceof User - ) { - await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId: bot.workspace, - acceptedRoles, - }); - - return bot; + + switch (authData.actor.type) { + case ActorType.USER: + await validateUserClientForWorkspace({ + user: authData.authPayload as IUser, + workspaceId: bot.workspace, + acceptedRoles, + }); + return bot; + case ActorType.SERVICE: + throw UnauthorizedRequestError({ + message: "Failed service token authorization for bot", + }); } - - if ( - authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && - authData.authPayload instanceof ServiceAccount - ) { - await validateServiceAccountClientForWorkspace({ - serviceAccount: authData.authPayload, - workspaceId: bot.workspace, - }); - - return bot; - } - - if ( - authData.authMode === AUTH_MODE_SERVICE_TOKEN && - authData.authPayload instanceof ServiceTokenData - ) { - throw UnauthorizedRequestError({ - message: "Failed service token authorization for bot", - }); - } - - if ( - authData.authMode === AUTH_MODE_API_KEY && - authData.authPayload instanceof User - ) { - await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId: bot.workspace, - acceptedRoles, - }); - - return bot; - } - - throw BotNotFoundError({ - message: "Failed client authorization for bot", - }); }; \ No newline at end of file diff --git a/backend/src/validation/integration.ts b/backend/src/validation/integration.ts index 3f39a87b6..b1143a9f7 100644 --- a/backend/src/validation/integration.ts +++ b/backend/src/validation/integration.ts @@ -1,15 +1,9 @@ import { Types } from "mongoose"; import { - IServiceAccount, - IServiceTokenData, IUser, Integration, IntegrationAuth, - ServiceAccount, - ServiceTokenData, - User, } from "../models"; -import { validateServiceAccountClientForWorkspace } from "./serviceAccount"; import { validateUserClientForWorkspace } from "./user"; import { IntegrationService } from "../services"; import { @@ -17,12 +11,8 @@ import { IntegrationNotFoundError, UnauthorizedRequestError, } from "../utils/errors"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, -} from "../variables"; +import { AuthData } from "../interfaces/middleware"; +import { ActorType } from "../ee/models"; /** * Validate authenticated clients for integration with id [integrationId] based @@ -39,10 +29,7 @@ export const validateClientForIntegration = async ({ integrationId, acceptedRoles, }: { - authData: { - authMode: string; - authPayload: IUser | IServiceAccount | IServiceTokenData; - }; + authData: AuthData; integrationId: Types.ObjectId; acceptedRoles: Array<"admin" | "member">; }) => { @@ -61,43 +48,19 @@ export const validateClientForIntegration = async ({ const accessToken = (await IntegrationService.getIntegrationAuthAccess({ integrationAuthId: integrationAuth._id, })).accessToken; - - if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { - await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId: integration.workspace, - acceptedRoles, - }); - - return ({ integration, accessToken }); - } - if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { - await validateServiceAccountClientForWorkspace({ - serviceAccount: authData.authPayload, - workspaceId: integration.workspace, - }); + switch (authData.actor.type) { + case ActorType.USER: + await validateUserClientForWorkspace({ + user: authData.authPayload as IUser, + workspaceId: integration.workspace, + acceptedRoles, + }); - return ({ integration, accessToken }); + return ({ integration, accessToken }); + case ActorType.SERVICE: + throw UnauthorizedRequestError({ + message: "Failed service token authorization for integration", + }); } - - if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { - throw UnauthorizedRequestError({ - message: "Failed service token authorization for integration", - }); - } - - if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { - await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId: integration.workspace, - acceptedRoles, - }); - - return ({ integration, accessToken }); - } - - throw UnauthorizedRequestError({ - message: "Failed client authorization for integration", - }); } \ No newline at end of file diff --git a/backend/src/validation/integrationAuth.ts b/backend/src/validation/integrationAuth.ts index fa77f3d62..676184324 100644 --- a/backend/src/validation/integrationAuth.ts +++ b/backend/src/validation/integrationAuth.ts @@ -1,27 +1,17 @@ import { Types } from "mongoose"; import { - IServiceAccount, - IServiceTokenData, IUser, IWorkspace, IntegrationAuth, - ServiceAccount, - ServiceTokenData, - User, } from "../models"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, -} from "../variables"; import { IntegrationAuthNotFoundError, UnauthorizedRequestError, } from "../utils/errors"; import { IntegrationService } from "../services"; import { validateUserClientForWorkspace } from "./user"; -import { validateServiceAccountClientForWorkspace } from "./serviceAccount"; +import { AuthData } from "../interfaces/middleware"; +import { ActorType } from "../ee/models"; /** * Validate authenticated clients for integration authorization with id [integrationAuthId] based @@ -38,10 +28,7 @@ import { validateServiceAccountClientForWorkspace } from "./serviceAccount"; acceptedRoles, attachAccessToken, }: { - authData: { - authMode: string; - authPayload: IUser | IServiceAccount | IServiceTokenData; - }; + authData: AuthData; integrationAuthId: Types.ObjectId; acceptedRoles: Array<"admin" | "member">; attachAccessToken?: boolean; @@ -66,44 +53,20 @@ import { validateServiceAccountClientForWorkspace } from "./serviceAccount"; accessId = access.accessId; } - if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { - await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId: integrationAuth.workspace._id, - acceptedRoles, - }); + switch (authData.actor.type) { + case ActorType.USER: + await validateUserClientForWorkspace({ + user: authData.authPayload as IUser, + workspaceId: integrationAuth.workspace._id, + acceptedRoles, + }); - return ({ integrationAuth, accessToken, accessId }); + return ({ integrationAuth, accessToken, accessId }); + case ActorType.SERVICE: + throw UnauthorizedRequestError({ + message: "Failed service token authorization for integration authorization", + }); } - - if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { - await validateServiceAccountClientForWorkspace({ - serviceAccount: authData.authPayload, - workspaceId: integrationAuth.workspace._id, - }); - - return ({ integrationAuth, accessToken, accessId }); - } - - if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { - throw UnauthorizedRequestError({ - message: "Failed service token authorization for integration authorization", - }); - } - - if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { - await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId: integrationAuth.workspace._id, - acceptedRoles, - }); - - return ({ integrationAuth, accessToken, accessId }); - } - - throw UnauthorizedRequestError({ - message: "Failed client authorization for integration authorization", - }); } export { diff --git a/backend/src/validation/membership.ts b/backend/src/validation/membership.ts index aee5f8bca..d788f4461 100644 --- a/backend/src/validation/membership.ts +++ b/backend/src/validation/membership.ts @@ -15,12 +15,9 @@ import { MembershipNotFoundError, UnauthorizedRequestError, } from "../utils/errors"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, -} from "../variables"; +import { AuthData } from "../interfaces/middleware"; +import { ActorType } from "../ee/models"; +import { auth } from "../routes/v1"; /** * Validate authenticated clients for membership with id [membershipId] based @@ -36,10 +33,7 @@ export const validateClientForMembership = async ({ membershipId, acceptedRoles, }: { - authData: { - authMode: string; - authPayload: IUser | IServiceAccount | IServiceTokenData; - }; + authData: AuthData; membershipId: Types.ObjectId; acceptedRoles: Array<"admin" | "member">; }) => { @@ -49,46 +43,22 @@ export const validateClientForMembership = async ({ if (!membership) throw MembershipNotFoundError({ message: "Failed to find membership", }); - - if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { - await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId: membership.workspace, - acceptedRoles, - }); - - return membership; - } - if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { - await validateServiceAccountClientForWorkspace({ - serviceAccount: authData.authPayload, - workspaceId: membership.workspace, - }); - - return membership; + switch (authData.actor.type) { + case ActorType.USER: + await validateUserClientForWorkspace({ + user: authData.authPayload as IUser, + workspaceId: membership.workspace, + acceptedRoles, + }); + + return membership; + case ActorType.SERVICE: + await validateServiceTokenDataClientForWorkspace({ + serviceTokenData: authData.authPayload as IServiceTokenData, + workspaceId: new Types.ObjectId(membership.workspace), + }); + + return membership; } - - if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { - await validateServiceTokenDataClientForWorkspace({ - serviceTokenData: authData.authPayload, - workspaceId: new Types.ObjectId(membership.workspace), - }); - - return membership; - } - - if (authData.authMode == AUTH_MODE_API_KEY && authData.authPayload instanceof User) { - await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId: membership.workspace, - acceptedRoles, - }); - - return membership; - } - - throw UnauthorizedRequestError({ - message: "Failed client authorization for membership", - }); } \ No newline at end of file diff --git a/backend/src/validation/membershipOrg.ts b/backend/src/validation/membershipOrg.ts index 444f2aa2a..b0ada6a61 100644 --- a/backend/src/validation/membershipOrg.ts +++ b/backend/src/validation/membershipOrg.ts @@ -1,12 +1,6 @@ import { Types } from "mongoose"; import { - IServiceAccount, - IServiceTokenData, - IUser, MembershipOrg, - ServiceAccount, - ServiceTokenData, - User, } from "../models"; import { validateMembershipOrg, @@ -15,12 +9,8 @@ import { MembershipOrgNotFoundError, UnauthorizedRequestError, } from "../utils/errors"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, -} from "../variables"; +import { AuthData } from "../interfaces/middleware"; +import { ActorType } from "../ee/models"; /** * Validate authenticated clients for organization membership with id [membershipOrgId] based @@ -37,10 +27,7 @@ export const validateClientForMembershipOrg = async ({ acceptedRoles, acceptedStatuses, }: { - authData: { - authMode: string; - authPayload: IUser | IServiceAccount | IServiceTokenData; - }; + authData: AuthData; membershipOrgId: Types.ObjectId; acceptedRoles: Array<"owner" | "admin" | "member">; acceptedStatuses: Array<"invited" | "accepted">; @@ -50,44 +37,20 @@ export const validateClientForMembershipOrg = async ({ if (!membershipOrg) throw MembershipOrgNotFoundError({ message: "Failed to find organization membership ", }); - - if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { - await validateMembershipOrg({ - userId: authData.authPayload._id, - organizationId: membershipOrg.organization, - acceptedRoles, - acceptedStatuses, - }); - - return membershipOrg; - } - - if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { - if (!authData.authPayload.organization.equals(membershipOrg.organization)) throw UnauthorizedRequestError({ - message: "Failed service account client authorization for organization membership", - }); - - return membershipOrg; - } - - if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { - throw UnauthorizedRequestError({ - message: "Failed service account client authorization for organization membership", - }); - } - if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { - await validateMembershipOrg({ - userId: authData.authPayload._id, - organizationId: membershipOrg.organization, - acceptedRoles, - acceptedStatuses, - }); - - return membershipOrg; + switch (authData.actor.type) { + case ActorType.USER: + await validateMembershipOrg({ + userId: authData.authPayload._id, + organizationId: membershipOrg.organization, + acceptedRoles, + acceptedStatuses, + }); + + return membershipOrg; + case ActorType.SERVICE: + throw UnauthorizedRequestError({ + message: "Failed service account client authorization for organization membership", + }); } - - throw UnauthorizedRequestError({ - message: "Failed client authorization for organization membership", - }); } \ No newline at end of file diff --git a/backend/src/validation/organization.ts b/backend/src/validation/organization.ts index 35e0aab04..c4838d154 100644 --- a/backend/src/validation/organization.ts +++ b/backend/src/validation/organization.ts @@ -1,25 +1,15 @@ import { Types } from "mongoose"; import { - IServiceAccount, - IServiceTokenData, IUser, Organization, - ServiceAccount, - ServiceTokenData, - User, } from "../models"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, -} from "../variables"; import { OrganizationNotFoundError, UnauthorizedRequestError, } from "../utils/errors"; import { validateUserClientForOrganization } from "./user"; -import { validateServiceAccountClientForOrganization } from "./serviceAccount"; +import { AuthData } from "../interfaces/middleware"; +import { ActorType } from "../ee/models"; /** * Validate accepted clients for organization with id [organizationId] @@ -33,10 +23,7 @@ export const validateClientForOrganization = async ({ acceptedRoles, acceptedStatuses, }: { - authData: { - authMode: string; - authPayload: IUser | IServiceAccount | IServiceTokenData; - }; + authData: AuthData; organizationId: Types.ObjectId; acceptedRoles: Array<"owner" | "admin" | "member">; acceptedStatuses: Array<"invited" | "accepted">; @@ -48,57 +35,20 @@ export const validateClientForOrganization = async ({ message: "Failed to find organization", }); } + + switch (authData.actor.type) { + case ActorType.USER: + const membershipOrg = await validateUserClientForOrganization({ + user: authData.authPayload as IUser, + organization, + acceptedRoles, + acceptedStatuses, + }); - if ( - authData.authMode === AUTH_MODE_JWT && - authData.authPayload instanceof User - ) { - const membershipOrg = await validateUserClientForOrganization({ - user: authData.authPayload, - organization, - acceptedRoles, - acceptedStatuses, - }); - - return { organization, membershipOrg }; + return { organization, membershipOrg }; + case ActorType.SERVICE: + throw UnauthorizedRequestError({ + message: "Failed service token authorization for organization", + }); } - - if ( - authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && - authData.authPayload instanceof ServiceAccount - ) { - await validateServiceAccountClientForOrganization({ - serviceAccount: authData.authPayload, - organization, - }); - - return { organization }; - } - - if ( - authData.authMode === AUTH_MODE_SERVICE_TOKEN && - authData.authPayload instanceof ServiceTokenData - ) { - throw UnauthorizedRequestError({ - message: "Failed service token authorization for organization", - }); - } - - if ( - authData.authMode === AUTH_MODE_API_KEY && - authData.authPayload instanceof User - ) { - const membershipOrg = await validateUserClientForOrganization({ - user: authData.authPayload, - organization, - acceptedRoles, - acceptedStatuses, - }); - - return { organization, membershipOrg }; - } - - throw UnauthorizedRequestError({ - message: "Failed client authorization for organization", - }); }; \ No newline at end of file diff --git a/backend/src/validation/secrets.ts b/backend/src/validation/secrets.ts index 0de983dbe..c51e83e23 100644 --- a/backend/src/validation/secrets.ts +++ b/backend/src/validation/secrets.ts @@ -2,25 +2,17 @@ import { Types } from "mongoose"; import { ISecret, Secret, - ServiceAccount, - ServiceTokenData, - User, + IUser, + IServiceTokenData, } from "../models"; -import { validateServiceAccountClientForSecrets, validateServiceAccountClientForWorkspace } from "./serviceAccount"; import { validateUserClientForSecret, validateUserClientForSecrets } from "./user"; import { validateServiceTokenDataClientForSecrets, validateServiceTokenDataClientForWorkspace } from "./serviceTokenData"; -import { AuthData } from "../interfaces/middleware"; import { BadRequestError, SecretNotFoundError, - UnauthorizedRequestError, } from "../utils/errors"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, -} from "../variables"; +import { AuthData } from "../interfaces/middleware"; +import { ActorType } from "../ee/models"; /** * Validate authenticated clients for secrets with id [secretId] based @@ -47,53 +39,26 @@ export const validateClientForSecret = async ({ if (!secret) throw SecretNotFoundError({ message: "Failed to find secret", }); + + switch (authData.actor.type) { + case ActorType.USER: + await validateUserClientForSecret({ + user: authData.authPayload as IUser, + secret, + acceptedRoles, + requiredPermissions, + }); - if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { - await validateUserClientForSecret({ - user: authData.authPayload, - secret, - acceptedRoles, - requiredPermissions, - }); - - return secret; - } - - if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { - await validateServiceAccountClientForWorkspace({ - serviceAccount: authData.authPayload, - workspaceId: secret.workspace, - environment: secret.environment, - requiredPermissions, - }); + return secret; + case ActorType.SERVICE: + await validateServiceTokenDataClientForWorkspace({ + serviceTokenData: authData.authPayload as IServiceTokenData, + workspaceId: secret.workspace, + environment: secret.environment, + }); - return secret; + return secret; } - - if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { - await validateServiceTokenDataClientForWorkspace({ - serviceTokenData: authData.authPayload, - workspaceId: secret.workspace, - environment: secret.environment, - }); - - return secret; - } - - if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { - await validateUserClientForSecret({ - user: authData.authPayload, - secret, - acceptedRoles, - requiredPermissions, - }); - - return secret; - } - - throw UnauthorizedRequestError({ - message: "Failed client authorization for secret", - }); } /** @@ -127,48 +92,23 @@ export const validateClientForSecrets = async ({ if (secrets.length != secretIds.length) { throw BadRequestError({ message: "Failed to validate non-existent secrets" }) } - - if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { - await validateUserClientForSecrets({ - user: authData.authPayload, - secrets, - requiredPermissions, - }); - - return secrets; - } - if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { - await validateServiceAccountClientForSecrets({ - serviceAccount: authData.authPayload, - secrets, - requiredPermissions, - }); - - return secrets; + switch (authData.actor.type) { + case ActorType.USER: + await validateUserClientForSecrets({ + user: authData.authPayload as IUser, + secrets, + requiredPermissions, + }); + + return secrets; + case ActorType.SERVICE: + await validateServiceTokenDataClientForSecrets({ + serviceTokenData: authData.authPayload as IServiceTokenData, + secrets, + requiredPermissions, + }); + + return secrets; } - - if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { - await validateServiceTokenDataClientForSecrets({ - serviceTokenData: authData.authPayload, - secrets, - requiredPermissions, - }); - - return secrets; - } - - if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { - await validateUserClientForSecrets({ - user: authData.authPayload, - secrets, - requiredPermissions, - }); - - return secrets; - } - - throw UnauthorizedRequestError({ - message: "Failed client authorization for secrets resource", - }); } \ No newline at end of file diff --git a/backend/src/validation/serviceAccount.ts b/backend/src/validation/serviceAccount.ts index 78f2c9e69..b4cb562c4 100644 --- a/backend/src/validation/serviceAccount.ts +++ b/backend/src/validation/serviceAccount.ts @@ -4,12 +4,9 @@ import { IOrganization, ISecret, IServiceAccount, - IServiceTokenData, IUser, ServiceAccount, ServiceAccountWorkspacePermission, - ServiceTokenData, - User, } from "../models"; import { validateUserClientForServiceAccount } from "./user"; import { @@ -18,23 +15,18 @@ import { UnauthorizedRequestError, } from "../utils/errors"; import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, 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: { - authMode: string; - authPayload: IUser | IServiceAccount | IServiceTokenData; - }, + authData: AuthData; serviceAccountId: Types.ObjectId; requiredPermissions?: string[]; }) => { @@ -46,45 +38,20 @@ export const validateClientForServiceAccount = async ({ }); } - if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { - await validateUserClientForServiceAccount({ - user: authData.authPayload, - serviceAccount, - requiredPermissions, - }); - - return serviceAccount; + 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", + }); } - - if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { - await validateServiceAccountClientForServiceAccount({ - serviceAccount: authData.authPayload, - targetServiceAccount: serviceAccount, - requiredPermissions, - }); - - return serviceAccount; - } - - if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { - throw UnauthorizedRequestError({ - message: "Failed service token authorization for service account resource", - }); - } - - if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { - await validateUserClientForServiceAccount({ - user: authData.authPayload, - serviceAccount, - requiredPermissions, - }); - - return serviceAccount; - } - - throw UnauthorizedRequestError({ - message: "Failed client authorization for service account resource", - }); } /** diff --git a/backend/src/validation/serviceTokenData.ts b/backend/src/validation/serviceTokenData.ts index 580bcbdb9..fe8e3bd0b 100644 --- a/backend/src/validation/serviceTokenData.ts +++ b/backend/src/validation/serviceTokenData.ts @@ -1,22 +1,14 @@ import { Types } from "mongoose"; import { ISecret, - IServiceAccount, IServiceTokenData, IUser, - ServiceAccount, ServiceTokenData, - User } from "../models"; import { ServiceTokenDataNotFoundError, UnauthorizedRequestError } from "../utils/errors"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN -} from "../variables"; import { validateUserClientForWorkspace } from "./user"; -import { validateServiceAccountClientForWorkspace } from "./serviceAccount"; +import { ActorType } from "../ee/models"; +import { AuthData } from "../interfaces/middleware"; /** * Validate authenticated clients for service token with id [serviceTokenId] based @@ -31,10 +23,7 @@ export const validateClientForServiceTokenData = async ({ serviceTokenDataId, acceptedRoles }: { - authData: { - authMode: string; - authPayload: IUser | IServiceAccount | IServiceTokenData; - }; + authData: AuthData; serviceTokenDataId: Types.ObjectId; acceptedRoles: Array<"admin" | "member">; }) => { @@ -42,55 +31,24 @@ export const validateClientForServiceTokenData = async ({ .select("+encryptedKey +iv +tag") .populate<{ user: IUser }>("user"); - if (!serviceTokenData) - throw ServiceTokenDataNotFoundError({ - message: "Failed to find service token data" - }); - - if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { - await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId: serviceTokenData.workspace, - acceptedRoles - }); - - return serviceTokenData; - } - - if ( - authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && - authData.authPayload instanceof ServiceAccount - ) { - await validateServiceAccountClientForWorkspace({ - serviceAccount: authData.authPayload, - workspaceId: serviceTokenData.workspace - }); - - return serviceTokenData; - } - - if ( - authData.authMode === AUTH_MODE_SERVICE_TOKEN && - authData.authPayload instanceof ServiceTokenData - ) { - throw UnauthorizedRequestError({ - message: "Failed service token authorization for service token data" - }); - } - - if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { - await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId: serviceTokenData.workspace, - acceptedRoles - }); - - return serviceTokenData; - } - - throw UnauthorizedRequestError({ - message: "Failed client authorization for service token data" + if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ + message: "Failed to find service token data" }); + + switch (authData.actor.type) { + case ActorType.USER: + await validateUserClientForWorkspace({ + user: authData.authPayload as IUser, + workspaceId: serviceTokenData.workspace, + acceptedRoles + }); + + return serviceTokenData; + case ActorType.SERVICE: + throw UnauthorizedRequestError({ + message: "Failed service token authorization for service token data" + }); + } }; /** diff --git a/backend/src/validation/workspace.ts b/backend/src/validation/workspace.ts index 618ccb02f..8c8f4408d 100644 --- a/backend/src/validation/workspace.ts +++ b/backend/src/validation/workspace.ts @@ -2,15 +2,14 @@ import net from "net"; import { Types } from "mongoose"; import { SecretBlindIndexData, - ServiceAccount, - ServiceTokenData, - User, + IServiceTokenData, + IUser, Workspace, } from "../models"; import { + ActorType, TrustedIP } from "../ee/models"; -import { validateServiceAccountClientForWorkspace } from "./serviceAccount"; import { validateUserClientForWorkspace } from "./user"; import { validateServiceTokenDataClientForWorkspace } from "./serviceTokenData"; import { @@ -18,12 +17,6 @@ import { UnauthorizedRequestError, WorkspaceNotFoundError, } from "../utils/errors"; -import { - AUTH_MODE_API_KEY, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, -} from "../variables"; import { BotService } from "../services"; import { AuthData } from "../interfaces/middleware"; import { extractIPDetails } from "../utils/ip"; @@ -85,89 +78,59 @@ export const validateClientForWorkspace = async ({ }); } - + switch (authData.actor.type) { + case ActorType.USER: + const membership = await validateUserClientForWorkspace({ + user: authData.authPayload as IUser, + workspaceId, + environment, + acceptedRoles, + requiredPermissions, + }); + + return ({ membership, workspace }); + case ActorType.SERVICE: + if (checkIPAllowlist) { + const trustedIps = await TrustedIP.find({ + workspace: workspaceId + }); + + if (trustedIps.length > 0) { + // case: check the IP address of the inbound request against trusted IPs - if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) { - const membership = await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId, - environment, - acceptedRoles, - requiredPermissions, - }); + const blockList = new net.BlockList(); - return ({ membership, workspace }); - } + for (const trustedIp of trustedIps) { + if (trustedIp.prefix !== undefined) { + blockList.addSubnet( + trustedIp.ipAddress, + trustedIp.prefix, + trustedIp.type + ); + } else { + blockList.addAddress( + trustedIp.ipAddress, + trustedIp.type + ); + } + } + + const { type } = extractIPDetails(authData.ipAddress); + const check = blockList.check(authData.ipAddress, type); + + if (!check) throw UnauthorizedRequestError({ + message: "Failed workspace authorization" + }); + } + } - if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { - await validateServiceAccountClientForWorkspace({ - serviceAccount: authData.authPayload, - workspaceId, - environment, - requiredPermissions, - }); - - return {}; - } - - if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) { - if (checkIPAllowlist) { - const trustedIps = await TrustedIP.find({ - workspace: workspaceId + await validateServiceTokenDataClientForWorkspace({ + serviceTokenData: authData.authPayload as IServiceTokenData, + workspaceId, + environment, + requiredPermissions, }); - if (trustedIps.length > 0) { - // case: check the IP address of the inbound request against trusted IPs - - const blockList = new net.BlockList(); - - for (const trustedIp of trustedIps) { - if (trustedIp.prefix !== undefined) { - blockList.addSubnet( - trustedIp.ipAddress, - trustedIp.prefix, - trustedIp.type - ); - } else { - blockList.addAddress( - trustedIp.ipAddress, - trustedIp.type - ); - } - } - - const { type } = extractIPDetails(authData.authIP); - const check = blockList.check(authData.authIP, type); - - if (!check) throw UnauthorizedRequestError({ - message: "Failed workspace authorization" - }); - } - } - - await validateServiceTokenDataClientForWorkspace({ - serviceTokenData: authData.authPayload, - workspaceId, - environment, - requiredPermissions, - }); - - return {}; + return {}; } - - if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) { - const membership = await validateUserClientForWorkspace({ - user: authData.authPayload, - workspaceId, - environment, - acceptedRoles, - requiredPermissions, - }); - - return ({ membership, workspace }); - } - - throw UnauthorizedRequestError({ - message: "Failed client authorization for workspace", - }); } diff --git a/backend/src/variables/authentication.ts b/backend/src/variables/authentication.ts index 38bfdfc2c..38bd54dd3 100644 --- a/backend/src/variables/authentication.ts +++ b/backend/src/variables/authentication.ts @@ -1,4 +1,5 @@ -export const AUTH_MODE_JWT = "jwt"; -export const AUTH_MODE_SERVICE_ACCOUNT = "serviceAccount"; -export const AUTH_MODE_SERVICE_TOKEN = "serviceToken"; -export const AUTH_MODE_API_KEY = "apiKey"; // TODO: deprecate \ No newline at end of file +export enum AuthMode { + JWT = "jwt", + SERVICE_TOKEN = "serviceToken", + API_KEY = "apiKey" +} \ No newline at end of file diff --git a/frontend/src/ee/components/ActivitySideBar.tsx b/frontend/src/ee/components/ActivitySideBar.tsx index 274704f06..67d673c3d 100644 --- a/frontend/src/ee/components/ActivitySideBar.tsx +++ b/frontend/src/ee/components/ActivitySideBar.tsx @@ -1,3 +1,4 @@ +// TODO: deprecate in favor of new audit logs import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import Image from "next/image"; diff --git a/frontend/src/ee/components/ActivityTable.tsx b/frontend/src/ee/components/ActivityTable.tsx index b98f2dd7e..63640e15a 100644 --- a/frontend/src/ee/components/ActivityTable.tsx +++ b/frontend/src/ee/components/ActivityTable.tsx @@ -1,3 +1,5 @@ +// 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"; diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx new file mode 100644 index 000000000..f54b20b1a --- /dev/null +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -0,0 +1,16 @@ +import { EventType, UserAgentType } from "./enums"; + +export const eventToNameMap: { [K in EventType]: string } = { + [EventType.GET_SECRETS]: "Get Secrets", + [EventType.GET_SECRET]: "Get Secret", + [EventType.CREATE_SECRET]: "Create Secret", + [EventType.UPDATE_SECRET]: "Update Secret", + [EventType.DELETE_SECRET]: "Delete Secret", +}; + +export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = { + [UserAgentType.WEB]: "Web", + [UserAgentType.CLI]: "CLI", + [UserAgentType.K8_OPERATOR]: "K8s operator", + [UserAgentType.OTHER]: "Other", +}; \ No newline at end of file diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx new file mode 100644 index 000000000..4c75f6395 --- /dev/null +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -0,0 +1,19 @@ +export enum ActorType { + USER = "user", + SERVICE = "service" +} + +export enum UserAgentType { + WEB = "web", + CLI = "cli", + K8_OPERATOR = "k8-operator", + OTHER = "other" +} + +export enum EventType { + GET_SECRETS = "get-secrets", + GET_SECRET = "get-secret", + CREATE_SECRET = "create-secret", + UPDATE_SECRET = "update-secret", + DELETE_SECRET = "delete-secret" +} \ No newline at end of file diff --git a/frontend/src/hooks/api/auditLogs/index.tsx b/frontend/src/hooks/api/auditLogs/index.tsx new file mode 100644 index 000000000..fb026aebb --- /dev/null +++ b/frontend/src/hooks/api/auditLogs/index.tsx @@ -0,0 +1 @@ +export * from "./queries"; \ No newline at end of file diff --git a/frontend/src/hooks/api/auditLogs/queries.tsx b/frontend/src/hooks/api/auditLogs/queries.tsx new file mode 100644 index 000000000..606794876 --- /dev/null +++ b/frontend/src/hooks/api/auditLogs/queries.tsx @@ -0,0 +1,53 @@ +import { useQuery } from "@tanstack/react-query"; +import { apiRequest } from "@app/config/request"; +import { + AuditLog, + Actor +} from "./types"; +import { EventType, UserAgentType } from "./enums"; + +export const workspaceKeys = { + getAuditLogs: (workspaceId: string, filters: { + eventType?: EventType; + userAgentType?: UserAgentType; + actor?: string; + }) => [{ workspaceId, filters }, "audit-logs"] as const, + getAuditLogActorFilterOpts: (workspaceId: string) => [{ workspaceId }, "audit-log-actor-filters"] as const +} + +export const useGetAuditLogs = (workspaceId: string, filters: { + eventType?: EventType; + userAgentType?: UserAgentType; + actor?: string; +}) => { + return useQuery({ + queryKey: workspaceKeys.getAuditLogs(workspaceId, filters), + queryFn: async () => { + const params = new URLSearchParams(); + if (filters.eventType) { + params.append("eventType", filters.eventType); + } + + if (filters.userAgentType) { + params.append("userAgentType", filters.userAgentType); + } + + if (filters.actor) { + params.append("actor", filters.actor); + } + + const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>(`/api/v1/workspace/${workspaceId}/audit-logs`, { params }); + return data.auditLogs; + } + }); +} + +export const useGetAuditLogActorFilterOpts = (workspaceId: string) => { + return useQuery({ + queryKey: workspaceKeys.getAuditLogActorFilterOpts(workspaceId), + queryFn: async () => { + const { data } = await apiRequest.get<{ actors: Actor[] }>(`/api/v1/workspace/${workspaceId}/audit-logs/filters/actors`); + return data.actors; + } + }); +} \ No newline at end of file diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx new file mode 100644 index 000000000..e836521ec --- /dev/null +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -0,0 +1,103 @@ +import { + ActorType, + EventType, + UserAgentType +} from "./enums"; + +interface UserActorMetadata { + userId: string; + email: string; +} + +interface ServiceActorMetadata { + serviceId: string; + name: string; +} + + +interface UserActor { + type: ActorType.USER; + metadata: UserActorMetadata; +} + +export interface ServiceActor { + type: ActorType.SERVICE; + metadata: ServiceActorMetadata; +} + +export type Actor = + | UserActor + | ServiceActor; + +interface GetSecretsEvent { + type: EventType.GET_SECRETS; + metadata: { + environment: string; + secretPath: string; + numberOfSecrets: number; + }; +} + +interface GetSecretEvent { + type: EventType.GET_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + }; +} + +interface CreateSecretEvent { + type: EventType.CREATE_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + } +} + +interface UpdateSecretEvent { + type: EventType.UPDATE_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + } +} + +interface DeleteSecretEvent { + type: EventType.DELETE_SECRET; + metadata: { + environment: string; + secretPath: string; + secretId: string; + secretKey: string; + secretVersion: number; + } +} + +export type Event = + | GetSecretsEvent + | GetSecretEvent + | CreateSecretEvent + | UpdateSecretEvent + | DeleteSecretEvent; + +export type AuditLog = { + _id: string; + actor: Actor; + organization: string; + workspace: string; + ipAddress: string; + event: Event; + userAgent: string; + userAgentType: UserAgentType; + createdAt: string; + updatedAt: string; +} diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index 672e51052..c5839c9bf 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -15,6 +15,7 @@ export * from "./ssoConfig"; export * from "./subscriptions"; export * from "./tags"; export * from "./trustedIps"; +export * from "./auditLogs"; export * from "./users"; export * from "./webhooks"; export * from "./workspace"; diff --git a/frontend/src/hooks/api/subscriptions/types.ts b/frontend/src/hooks/api/subscriptions/types.ts index 433092db9..3fcdbb339 100644 --- a/frontend/src/hooks/api/subscriptions/types.ts +++ b/frontend/src/hooks/api/subscriptions/types.ts @@ -3,6 +3,7 @@ export type SubscriptionPlan = { membersUsed: number; memberLimit: number; auditLogs: boolean; + auditLogsRetentionDays: number; customAlerts: boolean; customRateLimits: boolean; pitRecovery: boolean; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index 0e3233452..9833c06fd 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -28,7 +28,8 @@ export const workspaceKeys = { getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"], getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"], getAllUserWorkspace: ["workspaces"] as const, - getUserWsEnvironments: (workspaceId: string) => ["workspace-env", { workspaceId }] as const + getUserWsEnvironments: (workspaceId: string) => ["workspace-env", { workspaceId }] as const, + getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const }; const fetchWorkspaceById = async (workspaceId: string) => { @@ -259,3 +260,4 @@ export const useDeleteWsEnvironment = () => { } }); }; + diff --git a/frontend/src/hooks/api/workspace/types.ts b/frontend/src/hooks/api/workspace/types.ts index 3f58f39b9..82a89a9ba 100644 --- a/frontend/src/hooks/api/workspace/types.ts +++ b/frontend/src/hooks/api/workspace/types.ts @@ -53,4 +53,4 @@ export type UpdateEnvironmentDTO = { environmentName: string; }; -export type DeleteEnvironmentDTO = { workspaceID: string; environmentSlug: string }; +export type DeleteEnvironmentDTO = { workspaceID: string; environmentSlug: string }; \ No newline at end of file diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index fea50dd8a..1df7517d4 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -483,6 +483,18 @@ export const AppLayout = ({ children }: LayoutProps) => { + + + + Audit Logs V2 + + + { + const { t } = useTranslation(); + + return ( +
+ + {t("common.head-title", { title: t("billing.title") })} + + + + +
+ ); +} + +export default Logs; + +Logs.requireAuth = true; \ No newline at end of file diff --git a/frontend/src/views/Project/LogsPage/LogsPage.tsx b/frontend/src/views/Project/LogsPage/LogsPage.tsx new file mode 100644 index 000000000..c17fd7092 --- /dev/null +++ b/frontend/src/views/Project/LogsPage/LogsPage.tsx @@ -0,0 +1,17 @@ +import { + LogsSection +} from "./components"; + +export const LogsPage = () => { + return ( +
+
+
+

Audit Logs

+
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx b/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx new file mode 100644 index 000000000..e86c1ae15 --- /dev/null +++ b/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx @@ -0,0 +1,147 @@ +import { Control, Controller, UseFormReset } from "react-hook-form"; +import { + FormControl, + Select, + SelectItem, + Button +} from "@app/components/v2"; +import { eventToNameMap, userAgentTTypeoNameMap } from "~/hooks/api/auditLogs/constants"; +import { useWorkspace } from "@app/context"; +import { useGetAuditLogActorFilterOpts } from "@app/hooks/api"; +import { Actor } from "~/hooks/api/auditLogs/types"; +import { ActorType } from "~/hooks/api/auditLogs/enums"; +import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AuditLogFilterFormData } from "./LogsSection"; + +const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value })); +const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label]) => ({ label, value })); + +type Props = { + control: Control; + reset: UseFormReset; +} + +export const LogsFilter = ({ + control, + reset +}: Props) => { + const { currentWorkspace } = useWorkspace(); + const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?._id ?? ""); + + const renderActorSelectItem = (actor: Actor) => { + switch (actor.type) { + case ActorType.USER: + return ( + + {actor.metadata.email} + + ); + case ActorType.SERVICE: + return ( + + {actor.metadata.name} + + ); + } + } + + return ( +
+
+
+ ( + + + + )} + /> +
+ {!isLoading && data && data.length > 0 && ( +
+ ( + + + + )} + /> +
+ )} +
+ ( + + + + )} + /> +
+
+
+ +
+
+ ); +} diff --git a/frontend/src/views/Project/LogsPage/components/LogsSection.tsx b/frontend/src/views/Project/LogsPage/components/LogsSection.tsx new file mode 100644 index 000000000..e9db22c53 --- /dev/null +++ b/frontend/src/views/Project/LogsPage/components/LogsSection.tsx @@ -0,0 +1,49 @@ +import { useForm } from "react-hook-form"; +import { LogsFilter } from "./LogsFilter"; +import { LogsTable } from "./LogsTable"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import { EventType, UserAgentType } from "~/hooks/api/auditLogs/enums"; + +const schema = yup.object({ + eventType: yup.string() + .oneOf(Object.values(EventType), 'Invalid event type'), + actor: yup.string(), + userAgentType: yup.string() + .oneOf(Object.values(UserAgentType), 'Invalid user agent type'), +}).required(); + +export type AuditLogFilterFormData = yup.InferType; + +export const LogsSection = () => { + const { + control, + reset, + watch, + } = useForm({ + resolver: yupResolver(schema) + }); + + const eventType = watch("eventType") as EventType | undefined; + const userAgentType = watch("userAgentType") as UserAgentType | undefined; + const actor = watch("actor") as string | undefined; + + return ( +
+
+

+ Audit Logs +

+
+ + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/views/Project/LogsPage/components/LogsTable.tsx b/frontend/src/views/Project/LogsPage/components/LogsTable.tsx new file mode 100644 index 000000000..8b0f769c3 --- /dev/null +++ b/frontend/src/views/Project/LogsPage/components/LogsTable.tsx @@ -0,0 +1,70 @@ +import { faFile } from "@fortawesome/free-solid-svg-icons"; +import { + EmptyState, + Table, + TableContainer, + TableSkeleton, + TBody, + Td, + Th, + THead, + Tr +} from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useGetAuditLogs } from "@app/hooks/api"; +import { LogsTableRow } from "./LogsTableRow"; +import { EventType, UserAgentType } from "~/hooks/api/auditLogs/enums"; + +type Props = { + eventType: EventType | undefined; + userAgentType: UserAgentType | undefined; + actor: string | undefined; +} + +export const LogsTable = ({ + eventType, + userAgentType, + actor +}: Props) => { + const { currentWorkspace } = useWorkspace(); + const { data, isLoading } = useGetAuditLogs(currentWorkspace?._id ?? "", { + eventType, + userAgentType, + actor + }); + + return ( + + + + + + + + + + + + + {!isLoading && data && data.map((auditLog) => ( + + ))} + {isLoading && } + {!isLoading && data && data.length === 0 && ( + + + + )} + +
TimestampEventActorSourceMetadata
+ +
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx b/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx new file mode 100644 index 000000000..152fbd12f --- /dev/null +++ b/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx @@ -0,0 +1,114 @@ +import { AuditLog, Actor, Event } from "~/hooks/api/auditLogs/types"; +import { ActorType, EventType } from "~/hooks/api/auditLogs/enums"; +import { eventToNameMap, userAgentTTypeoNameMap } from "~/hooks/api/auditLogs/constants"; +import { + Td, + Tr +} from "@app/components/v2"; + +type Props = { + auditLog: AuditLog +} + +export const LogsTableRow = ({ + auditLog +}: Props) => { + const renderActor = (actor: Actor) => { + switch (actor.type) { + case ActorType.USER: + return ( + +

{actor.metadata.email}

+

User

+ + ); + case ActorType.SERVICE: + return ( + +

{`${actor.metadata.name}`}

+

Service token

+ + ); + } + } + + const renderMetadata = (event: Event) => { + switch (event.type) { + case EventType.GET_SECRETS: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Path: ${event.metadata.secretPath}`}

+

{`# Secrets: ${event.metadata.numberOfSecrets}`}

+ + ); + case EventType.GET_SECRET: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Path: ${event.metadata.secretPath}`}

+

{`Secret: ${event.metadata.secretKey}`}

+ + ); + case EventType.CREATE_SECRET: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Path: ${event.metadata.secretPath}`}

+

{`Secret: ${event.metadata.secretKey}`}

+ + ); + case EventType.UPDATE_SECRET: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Path: ${event.metadata.secretPath}`}

+

{`Secret: ${event.metadata.secretKey}`}

+ + ); + case EventType.DELETE_SECRET: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Path: ${event.metadata.secretPath}`}

+

{`Secret: ${event.metadata.secretKey}`}

+ + ); + default: + return ( + Test + ); + } + } + + const formatDate = (dateToFormat: string) => { + const date = new Date(dateToFormat); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + + let hours = date.getHours(); + const minutes = String(date.getMinutes()).padStart(2, '0'); + + // convert from 24h to 12h format + const period = hours >= 12 ? 'PM' : 'AM'; + hours = hours % 12; + hours = hours ? hours : 12; // the hour '0' should be '12' + + const formattedDate = `${day}-${month}-${year} at ${hours}:${minutes} ${period}`; + return formattedDate; + } + + return ( + + {formatDate(auditLog.createdAt)} + {`${eventToNameMap[auditLog.event.type]}`} + {renderActor(auditLog.actor)} + +

{userAgentTTypeoNameMap[auditLog.userAgentType]}

+

{auditLog.ipAddress}

+ + {renderMetadata(auditLog.event)} + + ); +} \ No newline at end of file diff --git a/frontend/src/views/Project/LogsPage/components/index.tsx b/frontend/src/views/Project/LogsPage/components/index.tsx new file mode 100644 index 000000000..009b8fecd --- /dev/null +++ b/frontend/src/views/Project/LogsPage/components/index.tsx @@ -0,0 +1 @@ +export { LogsSection } from "./LogsSection"; \ No newline at end of file diff --git a/frontend/src/views/Project/LogsPage/index.tsx b/frontend/src/views/Project/LogsPage/index.tsx new file mode 100644 index 000000000..9e25d1a16 --- /dev/null +++ b/frontend/src/views/Project/LogsPage/index.tsx @@ -0,0 +1 @@ +export { LogsPage } from "./LogsPage"; \ No newline at end of file From 373dfff8e062fcbd182f93f79d8d3625f1a3230e Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sat, 5 Aug 2023 17:06:49 +0700 Subject: [PATCH 2/9] Remove print statement --- backend/src/middleware/requireWorkspaceAuth.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/middleware/requireWorkspaceAuth.ts b/backend/src/middleware/requireWorkspaceAuth.ts index e5e1e447d..f6f7405a9 100644 --- a/backend/src/middleware/requireWorkspaceAuth.ts +++ b/backend/src/middleware/requireWorkspaceAuth.ts @@ -32,8 +32,6 @@ const requireWorkspaceAuth = ({ const workspaceId = req[locationWorkspaceId]?.workspaceId; const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined; - console.log("workspaceId: ", workspaceId); - // validate clients const { membership, workspace } = await validateClientForWorkspace({ authData: req.authData, From 648968c4539e5eed1a2549a2d5ffade96ec160f6 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 7 Aug 2023 11:25:06 +0700 Subject: [PATCH 3/9] Run linter --- .../ee/controllers/v1/workspaceController.ts | 21 +++++++----- backend/src/ee/routes/v1/organizations.ts | 2 +- backend/src/ee/routes/v1/secret.ts | 4 +-- backend/src/ee/routes/v1/secretSnapshot.ts | 2 +- backend/src/ee/routes/v1/sso.ts | 4 +-- backend/src/ee/routes/v1/workspace.ts | 6 ++-- backend/src/ee/services/EEAuditLogService.ts | 4 +-- backend/src/helpers/auth.ts | 4 +-- backend/src/helpers/secrets.ts | 4 +-- backend/src/interfaces/middleware/index.ts | 2 +- backend/src/routes/v1/bot.ts | 2 +- backend/src/routes/v1/integration.ts | 4 +-- backend/src/routes/v1/integrationAuth.ts | 4 +-- backend/src/routes/v1/key.ts | 2 +- backend/src/routes/v1/organization.ts | 4 +-- backend/src/routes/v1/secret.ts | 4 +-- backend/src/routes/v1/secretImport.ts | 2 +- backend/src/routes/v1/secretsFolder.ts | 2 +- backend/src/routes/v1/serviceToken.ts | 4 +-- backend/src/routes/v1/webhook.ts | 2 +- backend/src/routes/v1/workspace.ts | 4 +-- backend/src/routes/v2/environment.ts | 4 +-- backend/src/routes/v2/organizations.ts | 4 +-- backend/src/routes/v2/secret.ts | 2 +- backend/src/routes/v2/secrets.ts | 2 +- backend/src/routes/v2/serviceTokenData.ts | 2 +- backend/src/routes/v2/tags.ts | 4 +-- backend/src/routes/v2/workspace.ts | 4 +-- backend/src/validation/membership.ts | 7 ---- backend/src/validation/organization.ts | 3 +- backend/src/validation/secrets.ts | 4 +-- backend/src/validation/workspace.ts | 7 ++-- frontend/src/hooks/api/auditLogs/queries.tsx | 9 ++--- frontend/src/hooks/api/index.tsx | 2 +- .../src/pages/integrations/checkly/create.tsx | 1 + .../LogsPage/components/LogsFilter.tsx | 33 ++++++++++++------- .../LogsPage/components/LogsSection.tsx | 20 ++++------- .../Project/LogsPage/components/LogsTable.tsx | 5 ++- .../LogsPage/components/LogsTableRow.tsx | 23 ++++++++----- .../Project/LogsPage/components/types.tsx | 13 ++++++++ 40 files changed, 130 insertions(+), 106 deletions(-) create mode 100644 frontend/src/views/Project/LogsPage/components/types.tsx diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index c865dc8eb..a77cdca71 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -1,20 +1,19 @@ import { Request, Response } from "express"; import { PipelineStage, Types } from "mongoose"; -import { Secret, Membership, User, ServiceTokenData } from "../../../models"; +import { Membership, Secret, ServiceTokenData, User } from "../../../models"; import { + ActorType, + AuditLog, FolderVersion, IPType, ISecretVersion, Log, SecretSnapshot, SecretVersion, + ServiceActor, TFolderRootVersionSchema, TrustedIP, - AuditLog, - Actor, - ActorType, - UserActor, - ServiceActor + UserActor } from "../../models"; import { EESecretService } from "../../services"; import { getLatestSecretVersionIds } from "../../helpers/secretVersion"; @@ -599,7 +598,7 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => { }; /** - * Return trusted ips for workspace with id [workspaceId] + * Return audit logs for workspace with id [workspaceId] * @param req * @param res */ @@ -608,6 +607,8 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { const eventType = req.query.eventType; const userAgentType = req.query.userAgentType; const actor = req.query.actor as string | undefined; + const offset: number = parseInt(req.query.offset as string); + const limit: number = parseInt(req.query.limit as string); const auditLogs = await AuditLog.find({ workspace: new Types.ObjectId(workspaceId), @@ -626,7 +627,9 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { }) } : {}) }) - .sort({ createdAt: -1 }); + .sort({ createdAt: -1 }) + .skip(offset) + .limit(limit); return res.status(200).send({ auditLogs @@ -634,7 +637,7 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { } /** - * Return trusted ips for workspace with id [workspaceId] + * Return audit log actor filter options for workspace with id [workspaceId] * @param req * @param res */ diff --git a/backend/src/ee/routes/v1/organizations.ts b/backend/src/ee/routes/v1/organizations.ts index d41d232da..6506d3afa 100644 --- a/backend/src/ee/routes/v1/organizations.ts +++ b/backend/src/ee/routes/v1/organizations.ts @@ -8,7 +8,7 @@ import { import { body, param, query } from "express-validator"; import { organizationsController } from "../../controllers/v1"; import { - ACCEPTED, ADMIN, MEMBER, OWNER, AuthMode + ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER } from "../../../variables"; router.get( diff --git a/backend/src/ee/routes/v1/secret.ts b/backend/src/ee/routes/v1/secret.ts index 376922e74..0eb23ee80 100644 --- a/backend/src/ee/routes/v1/secret.ts +++ b/backend/src/ee/routes/v1/secret.ts @@ -9,10 +9,10 @@ import { body, param, query } from "express-validator"; import { secretController } from "../../controllers/v1"; import { ADMIN, + AuthMode, MEMBER, PERMISSION_READ_SECRETS, - PERMISSION_WRITE_SECRETS, - AuthMode + PERMISSION_WRITE_SECRETS } from "../../../variables"; router.get( diff --git a/backend/src/ee/routes/v1/secretSnapshot.ts b/backend/src/ee/routes/v1/secretSnapshot.ts index c6201cc12..ecfe47ca5 100644 --- a/backend/src/ee/routes/v1/secretSnapshot.ts +++ b/backend/src/ee/routes/v1/secretSnapshot.ts @@ -8,7 +8,7 @@ import { validateRequest, } from "../../../middleware"; import { param } from "express-validator"; -import { ADMIN, MEMBER, AuthMode } from "../../../variables"; +import { ADMIN, AuthMode, MEMBER } from "../../../variables"; import { secretSnapshotController } from "../../controllers/v1"; router.get( diff --git a/backend/src/ee/routes/v1/sso.ts b/backend/src/ee/routes/v1/sso.ts index 22ecee9f0..005f84a0e 100644 --- a/backend/src/ee/routes/v1/sso.ts +++ b/backend/src/ee/routes/v1/sso.ts @@ -15,8 +15,8 @@ import { authLimiter } from "../../../helpers/rateLimiter"; import { ACCEPTED, ADMIN, - OWNER, - AuthMode + AuthMode, + OWNER } from "../../../variables"; router.get( diff --git a/backend/src/ee/routes/v1/workspace.ts b/backend/src/ee/routes/v1/workspace.ts index 407f5d3f8..90eea76e6 100644 --- a/backend/src/ee/routes/v1/workspace.ts +++ b/backend/src/ee/routes/v1/workspace.ts @@ -8,8 +8,8 @@ import { import { body, param, query } from "express-validator"; import { ADMIN, - MEMBER, - AuthMode + AuthMode, + MEMBER } from "../../../variables"; import { workspaceController } from "../../controllers/v1"; import { EventType, UserAgentType } from "../../models"; @@ -97,6 +97,8 @@ router.get( query("eventType").isString().isIn(Object.values(EventType)).optional({ nullable: true }), query("userAgentType").isString().isIn(Object.values(UserAgentType)).optional({ nullable: true }), query("actor").isString().optional({ nullable: true }), + query("offset").isString().default("0"), + query("limit").isString().default("20"), validateRequest, workspaceController.getWorkspaceAuditLogs ); diff --git a/backend/src/ee/services/EEAuditLogService.ts b/backend/src/ee/services/EEAuditLogService.ts index 02ea99225..035742077 100644 --- a/backend/src/ee/services/EEAuditLogService.ts +++ b/backend/src/ee/services/EEAuditLogService.ts @@ -11,8 +11,8 @@ interface EventScope { } type ValidEventScope = - | Required> - | Required> + | Required> + | Required> | Required export default class EEAuditLogService { diff --git a/backend/src/helpers/auth.ts b/backend/src/helpers/auth.ts index 50b68e62c..8d034dfbf 100644 --- a/backend/src/helpers/auth.ts +++ b/backend/src/helpers/auth.ts @@ -28,8 +28,8 @@ import { AuthMode } from "../variables"; import { - UserAuthData, - ServiceTokenAuthData + ServiceTokenAuthData, + UserAuthData } from "../interfaces/middleware"; import { ActorType } from "../ee/models"; diff --git a/backend/src/helpers/secrets.ts b/backend/src/helpers/secrets.ts index 0d5562b4e..880e7cee3 100644 --- a/backend/src/helpers/secrets.ts +++ b/backend/src/helpers/secrets.ts @@ -13,7 +13,7 @@ import { SecretBlindIndexData, ServiceTokenData, } from "../models"; -import { SecretVersion, EventType } from "../ee/models"; +import { EventType, SecretVersion } from "../ee/models"; import { BadRequestError, InternalServerError, @@ -40,7 +40,7 @@ import { } from "../utils/crypto"; import { TelemetryService } from "../services"; import { client, getEncryptionKey, getRootEncryptionKey } from "../config"; -import { EELogService, EESecretService, EEAuditLogService } from "../ee/services"; +import { EEAuditLogService, EELogService, EESecretService } from "../ee/services"; import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/auth"; import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService"; import picomatch from "picomatch"; diff --git a/backend/src/interfaces/middleware/index.ts b/backend/src/interfaces/middleware/index.ts index bb2435ecf..e8dd1b91b 100644 --- a/backend/src/interfaces/middleware/index.ts +++ b/backend/src/interfaces/middleware/index.ts @@ -4,8 +4,8 @@ import { IUser, } from "../../models"; import { - UserActor, ServiceActor, + UserActor, UserAgentType } from "../../ee/models"; diff --git a/backend/src/routes/v1/bot.ts b/backend/src/routes/v1/bot.ts index 536e0a3bd..e50d35625 100644 --- a/backend/src/routes/v1/bot.ts +++ b/backend/src/routes/v1/bot.ts @@ -8,7 +8,7 @@ import { validateRequest, } from "../../middleware"; import { botController } from "../../controllers/v1"; -import { ADMIN, MEMBER, AuthMode } from "../../variables"; +import { ADMIN, AuthMode, MEMBER } from "../../variables"; router.get( "/:workspaceId", diff --git a/backend/src/routes/v1/integration.ts b/backend/src/routes/v1/integration.ts index 0bd1dfbc6..95ec4ec2d 100644 --- a/backend/src/routes/v1/integration.ts +++ b/backend/src/routes/v1/integration.ts @@ -8,8 +8,8 @@ import { } from "../../middleware"; import { ADMIN, - MEMBER, - AuthMode + AuthMode, + MEMBER } from "../../variables"; import { body, param } from "express-validator"; import { integrationController } from "../../controllers/v1"; diff --git a/backend/src/routes/v1/integrationAuth.ts b/backend/src/routes/v1/integrationAuth.ts index daf880c60..b8b7f348d 100644 --- a/backend/src/routes/v1/integrationAuth.ts +++ b/backend/src/routes/v1/integrationAuth.ts @@ -9,8 +9,8 @@ import { } from "../../middleware"; import { ADMIN, - MEMBER, - AuthMode + AuthMode, + MEMBER } from "../../variables"; import { integrationAuthController } from "../../controllers/v1"; diff --git a/backend/src/routes/v1/key.ts b/backend/src/routes/v1/key.ts index daa840ee7..a12c8c0bb 100644 --- a/backend/src/routes/v1/key.ts +++ b/backend/src/routes/v1/key.ts @@ -6,7 +6,7 @@ import { validateRequest, } from "../../middleware"; import { body, param } from "express-validator"; -import { ADMIN, MEMBER, AuthMode } from "../../variables"; +import { ADMIN, AuthMode, MEMBER } from "../../variables"; import { keyController } from "../../controllers/v1"; router.post( diff --git a/backend/src/routes/v1/organization.ts b/backend/src/routes/v1/organization.ts index fdad1f9fa..8f4a924e9 100644 --- a/backend/src/routes/v1/organization.ts +++ b/backend/src/routes/v1/organization.ts @@ -9,9 +9,9 @@ import { import { ACCEPTED, ADMIN, + AuthMode, MEMBER, - OWNER, - AuthMode + OWNER } from "../../variables"; import { organizationController } from "../../controllers/v1"; diff --git a/backend/src/routes/v1/secret.ts b/backend/src/routes/v1/secret.ts index 6cebd4db8..10668de34 100644 --- a/backend/src/routes/v1/secret.ts +++ b/backend/src/routes/v1/secret.ts @@ -10,8 +10,8 @@ import { body, param, query } from "express-validator"; import { secretController } from "../../controllers/v1"; import { ADMIN, - MEMBER, - AuthMode + AuthMode, + MEMBER } from "../../variables"; // note to devs: these endpoints will be deprecated in favor of v2 diff --git a/backend/src/routes/v1/secretImport.ts b/backend/src/routes/v1/secretImport.ts index 2e7e22bc3..79ee8e238 100644 --- a/backend/src/routes/v1/secretImport.ts +++ b/backend/src/routes/v1/secretImport.ts @@ -3,7 +3,7 @@ const router = express.Router(); import { body, param, query } from "express-validator"; import { secretImportController } from "../../controllers/v1"; import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware"; -import { ADMIN, MEMBER, AuthMode } from "../../variables"; +import { ADMIN, AuthMode, MEMBER } from "../../variables"; router.post( "/", diff --git a/backend/src/routes/v1/secretsFolder.ts b/backend/src/routes/v1/secretsFolder.ts index 6a89e4b97..f7773367b 100644 --- a/backend/src/routes/v1/secretsFolder.ts +++ b/backend/src/routes/v1/secretsFolder.ts @@ -12,7 +12,7 @@ import { getFolders, updateFolderById, } from "../../controllers/v1/secretsFolderController"; -import { ADMIN, MEMBER, AuthMode } from "../../variables"; +import { ADMIN, AuthMode, MEMBER } from "../../variables"; router.post( "/", diff --git a/backend/src/routes/v1/serviceToken.ts b/backend/src/routes/v1/serviceToken.ts index a3974e217..aaf35e85f 100644 --- a/backend/src/routes/v1/serviceToken.ts +++ b/backend/src/routes/v1/serviceToken.ts @@ -9,8 +9,8 @@ import { import { body } from "express-validator"; import { ADMIN, - MEMBER, - AuthMode + AuthMode, + MEMBER } from "../../variables"; import { serviceTokenController } from "../../controllers/v1"; diff --git a/backend/src/routes/v1/webhook.ts b/backend/src/routes/v1/webhook.ts index 55c471ea3..16264d4ed 100644 --- a/backend/src/routes/v1/webhook.ts +++ b/backend/src/routes/v1/webhook.ts @@ -2,7 +2,7 @@ import express from "express"; const router = express.Router(); import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware"; import { body, param, query } from "express-validator"; -import { ADMIN, MEMBER, AuthMode } from "../../variables"; +import { ADMIN, AuthMode, MEMBER } from "../../variables"; import { webhookController } from "../../controllers/v1"; router.post( diff --git a/backend/src/routes/v1/workspace.ts b/backend/src/routes/v1/workspace.ts index 2fd178f52..9a4554be6 100644 --- a/backend/src/routes/v1/workspace.ts +++ b/backend/src/routes/v1/workspace.ts @@ -8,8 +8,8 @@ import { } from "../../middleware"; import { ADMIN, - MEMBER, - AuthMode + AuthMode, + MEMBER } from "../../variables"; import { membershipController, workspaceController } from "../../controllers/v1"; diff --git a/backend/src/routes/v2/environment.ts b/backend/src/routes/v2/environment.ts index f383aa2fd..e9e7fcad3 100644 --- a/backend/src/routes/v2/environment.ts +++ b/backend/src/routes/v2/environment.ts @@ -9,8 +9,8 @@ import { } from "../../middleware"; import { ADMIN, - MEMBER, - AuthMode + AuthMode, + MEMBER } from "../../variables"; router.post( diff --git a/backend/src/routes/v2/organizations.ts b/backend/src/routes/v2/organizations.ts index 6196796cd..b305c1c0a 100644 --- a/backend/src/routes/v2/organizations.ts +++ b/backend/src/routes/v2/organizations.ts @@ -10,9 +10,9 @@ import { body, param } from "express-validator"; import { ACCEPTED, ADMIN, + AuthMode, MEMBER, - OWNER, - AuthMode + OWNER } from "../../variables"; import { organizationsController } from "../../controllers/v2"; diff --git a/backend/src/routes/v2/secret.ts b/backend/src/routes/v2/secret.ts index 9b7526a99..d9fedda20 100644 --- a/backend/src/routes/v2/secret.ts +++ b/backend/src/routes/v2/secret.ts @@ -8,8 +8,8 @@ import { import { body, param, query } from "express-validator"; import { ADMIN, - MEMBER, AuthMode, + MEMBER, PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS, } from "../../variables"; diff --git a/backend/src/routes/v2/secrets.ts b/backend/src/routes/v2/secrets.ts index e347e11c3..52196e983 100644 --- a/backend/src/routes/v2/secrets.ts +++ b/backend/src/routes/v2/secrets.ts @@ -12,8 +12,8 @@ import { body, query } from "express-validator"; import { secretsController } from "../../controllers/v2"; import { ADMIN, - MEMBER, AuthMode, + MEMBER, PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS, SECRET_PERSONAL, diff --git a/backend/src/routes/v2/serviceTokenData.ts b/backend/src/routes/v2/serviceTokenData.ts index aafd1cd47..efdc905fe 100644 --- a/backend/src/routes/v2/serviceTokenData.ts +++ b/backend/src/routes/v2/serviceTokenData.ts @@ -9,8 +9,8 @@ import { import { body, param } from "express-validator"; import { ADMIN, - MEMBER, AuthMode, + MEMBER, PERMISSION_WRITE_SECRETS } from "../../variables"; import { serviceTokenDataController } from "../../controllers/v2"; diff --git a/backend/src/routes/v2/tags.ts b/backend/src/routes/v2/tags.ts index af82db6ac..7ccfd17cd 100644 --- a/backend/src/routes/v2/tags.ts +++ b/backend/src/routes/v2/tags.ts @@ -9,8 +9,8 @@ import { } from "../../middleware"; import { ADMIN, - MEMBER, - AuthMode + AuthMode, + MEMBER } from "../../variables"; router.get( diff --git a/backend/src/routes/v2/workspace.ts b/backend/src/routes/v2/workspace.ts index a93bad48e..77ed75eb1 100644 --- a/backend/src/routes/v2/workspace.ts +++ b/backend/src/routes/v2/workspace.ts @@ -9,8 +9,8 @@ import { } from "../../middleware"; import { ADMIN, - MEMBER, - AuthMode + AuthMode, + MEMBER } from "../../variables"; import { workspaceController } from "../../controllers/v2"; diff --git a/backend/src/validation/membership.ts b/backend/src/validation/membership.ts index d788f4461..6c40aa0aa 100644 --- a/backend/src/validation/membership.ts +++ b/backend/src/validation/membership.ts @@ -1,23 +1,16 @@ import { Types } from "mongoose"; import { - IServiceAccount, IServiceTokenData, IUser, Membership, - ServiceAccount, - ServiceTokenData, - User, } from "../models"; -import { validateServiceAccountClientForWorkspace } from "./serviceAccount"; import { validateUserClientForWorkspace } from "./user"; import { validateServiceTokenDataClientForWorkspace } from "./serviceTokenData"; import { MembershipNotFoundError, - UnauthorizedRequestError, } from "../utils/errors"; import { AuthData } from "../interfaces/middleware"; import { ActorType } from "../ee/models"; -import { auth } from "../routes/v1"; /** * Validate authenticated clients for membership with id [membershipId] based diff --git a/backend/src/validation/organization.ts b/backend/src/validation/organization.ts index c4838d154..4ca9811c5 100644 --- a/backend/src/validation/organization.ts +++ b/backend/src/validation/organization.ts @@ -36,9 +36,10 @@ export const validateClientForOrganization = async ({ }); } + let membershipOrg; switch (authData.actor.type) { case ActorType.USER: - const membershipOrg = await validateUserClientForOrganization({ + membershipOrg = await validateUserClientForOrganization({ user: authData.authPayload as IUser, organization, acceptedRoles, diff --git a/backend/src/validation/secrets.ts b/backend/src/validation/secrets.ts index c51e83e23..6c0ac3084 100644 --- a/backend/src/validation/secrets.ts +++ b/backend/src/validation/secrets.ts @@ -1,9 +1,9 @@ import { Types } from "mongoose"; import { ISecret, - Secret, - IUser, IServiceTokenData, + IUser, + Secret, } from "../models"; import { validateUserClientForSecret, validateUserClientForSecrets } from "./user"; import { validateServiceTokenDataClientForSecrets, validateServiceTokenDataClientForWorkspace } from "./serviceTokenData"; diff --git a/backend/src/validation/workspace.ts b/backend/src/validation/workspace.ts index 8c8f4408d..3be8d0dad 100644 --- a/backend/src/validation/workspace.ts +++ b/backend/src/validation/workspace.ts @@ -1,9 +1,9 @@ import net from "net"; import { Types } from "mongoose"; import { - SecretBlindIndexData, - IServiceTokenData, + IServiceTokenData, IUser, + SecretBlindIndexData, Workspace, } from "../models"; import { @@ -78,9 +78,10 @@ export const validateClientForWorkspace = async ({ }); } + let membership; switch (authData.actor.type) { case ActorType.USER: - const membership = await validateUserClientForWorkspace({ + membership = await validateUserClientForWorkspace({ user: authData.authPayload as IUser, workspaceId, environment, diff --git a/frontend/src/hooks/api/auditLogs/queries.tsx b/frontend/src/hooks/api/auditLogs/queries.tsx index 606794876..05148c941 100644 --- a/frontend/src/hooks/api/auditLogs/queries.tsx +++ b/frontend/src/hooks/api/auditLogs/queries.tsx @@ -1,10 +1,11 @@ import { useQuery } from "@tanstack/react-query"; + import { apiRequest } from "@app/config/request"; -import { - AuditLog, - Actor -} from "./types"; + import { EventType, UserAgentType } from "./enums"; +import { + Actor, + AuditLog} from "./types"; export const workspaceKeys = { getAuditLogs: (workspaceId: string, filters: { diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index c5839c9bf..4e5cb66f4 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -1,3 +1,4 @@ +export * from "./auditLogs"; export * from "./auth"; export * from "./bots"; export * from "./incidentContacts"; @@ -15,7 +16,6 @@ export * from "./ssoConfig"; export * from "./subscriptions"; export * from "./tags"; export * from "./trustedIps"; -export * from "./auditLogs"; export * from "./users"; export * from "./webhooks"; export * from "./workspace"; diff --git a/frontend/src/pages/integrations/checkly/create.tsx b/frontend/src/pages/integrations/checkly/create.tsx index de7dd9347..2ae0435cf 100644 --- a/frontend/src/pages/integrations/checkly/create.tsx +++ b/frontend/src/pages/integrations/checkly/create.tsx @@ -11,6 +11,7 @@ import { Select, SelectItem } from "@app/components/v2"; + import { useGetIntegrationAuthApps, useGetIntegrationAuthById diff --git a/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx b/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx index e86c1ae15..d1273f3aa 100644 --- a/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx +++ b/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx @@ -1,18 +1,20 @@ import { Control, Controller, UseFormReset } from "react-hook-form"; -import { - FormControl, - Select, - SelectItem, - Button -} from "@app/components/v2"; -import { eventToNameMap, userAgentTTypeoNameMap } from "~/hooks/api/auditLogs/constants"; -import { useWorkspace } from "@app/context"; -import { useGetAuditLogActorFilterOpts } from "@app/hooks/api"; -import { Actor } from "~/hooks/api/auditLogs/types"; -import { ActorType } from "~/hooks/api/auditLogs/enums"; import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { AuditLogFilterFormData } from "./LogsSection"; + +import { + Button, + FormControl, + Select, + SelectItem} from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useGetAuditLogActorFilterOpts } from "@app/hooks/api"; + +import { eventToNameMap, userAgentTTypeoNameMap } from "~/hooks/api/auditLogs/constants"; +import { ActorType } from "~/hooks/api/auditLogs/enums"; +import { Actor } from "~/hooks/api/auditLogs/types"; + +import { AuditLogFilterFormData } from "./types"; const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value })); const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label]) => ({ label, value })); @@ -43,7 +45,14 @@ export const LogsFilter = ({ {actor.metadata.name} ); + default: + return ( + + N/A + + ); } + } return ( diff --git a/frontend/src/views/Project/LogsPage/components/LogsSection.tsx b/frontend/src/views/Project/LogsPage/components/LogsSection.tsx index e9db22c53..5651d0ef0 100644 --- a/frontend/src/views/Project/LogsPage/components/LogsSection.tsx +++ b/frontend/src/views/Project/LogsPage/components/LogsSection.tsx @@ -1,19 +1,11 @@ import { useForm } from "react-hook-form"; -import { LogsFilter } from "./LogsFilter"; -import { LogsTable } from "./LogsTable"; import { yupResolver } from "@hookform/resolvers/yup"; -import * as yup from "yup"; + import { EventType, UserAgentType } from "~/hooks/api/auditLogs/enums"; -const schema = yup.object({ - eventType: yup.string() - .oneOf(Object.values(EventType), 'Invalid event type'), - actor: yup.string(), - userAgentType: yup.string() - .oneOf(Object.values(UserAgentType), 'Invalid user agent type'), -}).required(); - -export type AuditLogFilterFormData = yup.InferType; +import { LogsFilter } from "./LogsFilter"; +import { LogsTable } from "./LogsTable"; +import { AuditLogFilterFormData,auditLogFilterFormSchema } from "./types"; export const LogsSection = () => { const { @@ -21,7 +13,7 @@ export const LogsSection = () => { reset, watch, } = useForm({ - resolver: yupResolver(schema) + resolver: yupResolver(auditLogFilterFormSchema) }); const eventType = watch("eventType") as EventType | undefined; @@ -46,4 +38,4 @@ export const LogsSection = () => { />
); -} \ No newline at end of file + } \ No newline at end of file diff --git a/frontend/src/views/Project/LogsPage/components/LogsTable.tsx b/frontend/src/views/Project/LogsPage/components/LogsTable.tsx index 8b0f769c3..823e3f56d 100644 --- a/frontend/src/views/Project/LogsPage/components/LogsTable.tsx +++ b/frontend/src/views/Project/LogsPage/components/LogsTable.tsx @@ -1,4 +1,5 @@ import { faFile } from "@fortawesome/free-solid-svg-icons"; + import { EmptyState, Table, @@ -12,9 +13,11 @@ import { } from "@app/components/v2"; import { useWorkspace } from "@app/context"; import { useGetAuditLogs } from "@app/hooks/api"; -import { LogsTableRow } from "./LogsTableRow"; + import { EventType, UserAgentType } from "~/hooks/api/auditLogs/enums"; +import { LogsTableRow } from "./LogsTableRow"; + type Props = { eventType: EventType | undefined; userAgentType: UserAgentType | undefined; diff --git a/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx b/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx index 152fbd12f..05dfec9b0 100644 --- a/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx +++ b/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx @@ -1,11 +1,12 @@ -import { AuditLog, Actor, Event } from "~/hooks/api/auditLogs/types"; -import { ActorType, EventType } from "~/hooks/api/auditLogs/enums"; -import { eventToNameMap, userAgentTTypeoNameMap } from "~/hooks/api/auditLogs/constants"; import { Td, Tr } from "@app/components/v2"; +import { eventToNameMap, userAgentTTypeoNameMap } from "~/hooks/api/auditLogs/constants"; +import { ActorType, EventType } from "~/hooks/api/auditLogs/enums"; +import { Actor, AuditLog, Event } from "~/hooks/api/auditLogs/types"; + type Props = { auditLog: AuditLog } @@ -29,6 +30,10 @@ export const LogsTableRow = ({

Service token

); + default: + return ( + + ); } } @@ -84,16 +89,16 @@ export const LogsTableRow = ({ const formatDate = (dateToFormat: string) => { const date = new Date(dateToFormat); const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); let hours = date.getHours(); - const minutes = String(date.getMinutes()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, "0"); // convert from 24h to 12h format - const period = hours >= 12 ? 'PM' : 'AM'; - hours = hours % 12; - hours = hours ? hours : 12; // the hour '0' should be '12' + const period = hours >= 12 ? "PM" : "AM"; + hours %= 12; + hours = hours || 12; // the hour '0' should be '12' const formattedDate = `${day}-${month}-${year} at ${hours}:${minutes} ${period}`; return formattedDate; diff --git a/frontend/src/views/Project/LogsPage/components/types.tsx b/frontend/src/views/Project/LogsPage/components/types.tsx new file mode 100644 index 000000000..025818578 --- /dev/null +++ b/frontend/src/views/Project/LogsPage/components/types.tsx @@ -0,0 +1,13 @@ +import * as yup from "yup"; + +import { EventType, UserAgentType } from "~/hooks/api/auditLogs/enums"; + +export const auditLogFilterFormSchema = yup.object({ + eventType: yup.string() + .oneOf(Object.values(EventType), "Invalid event type"), + actor: yup.string(), + userAgentType: yup.string() + .oneOf(Object.values(UserAgentType), "Invalid user agent type"), +}).required(); + +export type AuditLogFilterFormData = yup.InferType; \ No newline at end of file From 2591161272baa3e93a34c73dddcc201ddc41d16e Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 8 Aug 2023 09:50:49 +0700 Subject: [PATCH 4/9] Add more audit log events --- .../v1/integrationAuthController.ts | 45 ++++ .../controllers/v1/integrationController.ts | 43 +++- .../controllers/v1/membershipController.ts | 39 ++- .../controllers/v1/secretsFolderController.ts | 86 ++++++- .../src/controllers/v1/webhookController.ts | 66 ++++- .../controllers/v2/environmentController.ts | 55 ++++- .../v2/serviceTokenDataController.ts | 35 ++- .../ee/controllers/v1/workspaceController.ts | 58 ++++- backend/src/ee/models/auditLog/enums.ts | 22 +- backend/src/ee/models/auditLog/types.ts | 217 ++++++++++++++++- backend/src/ee/routes/v1/workspace.ts | 4 +- backend/src/ee/services/EELicenseService.ts | 6 +- backend/src/services/FolderService.ts | 17 +- .../src/hooks/api/auditLogs/constants.tsx | 30 ++- frontend/src/hooks/api/auditLogs/enums.tsx | 30 ++- frontend/src/hooks/api/auditLogs/queries.tsx | 25 +- frontend/src/hooks/api/auditLogs/types.tsx | 225 +++++++++++++++++- .../Project/LogsPage/components/LogsTable.tsx | 4 +- .../LogsPage/components/LogsTableRow.tsx | 163 +++++++++++++ 19 files changed, 1117 insertions(+), 53 deletions(-) diff --git a/backend/src/controllers/v1/integrationAuthController.ts b/backend/src/controllers/v1/integrationAuthController.ts index 43d223ad6..f4c25d54b 100644 --- a/backend/src/controllers/v1/integrationAuthController.ts +++ b/backend/src/controllers/v1/integrationAuthController.ts @@ -3,7 +3,9 @@ import { Types } from "mongoose"; import { standardRequest } from "../../config/request"; import { getApps, getTeams, revokeAccess } from "../../integrations"; import { Bot, IntegrationAuth } from "../../models"; +import { EventType } from "../../ee/models"; import { IntegrationService } from "../../services"; +import { EEAuditLogService } from "../../ee/services"; import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_UTF8, @@ -62,6 +64,19 @@ export const oAuthExchange = async (req: Request, res: Response) => { environment: environments[0].slug }); + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.AUTHORIZE_INTEGRATION, + metadata: { + integration: integrationAuth.integration + } + }, + { + workspaceId: integrationAuth.workspace + } + ); + return res.status(200).send({ integrationAuth }); @@ -129,6 +144,19 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) => }); if (!integrationAuth) throw new Error("Failed to save integration access token"); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.AUTHORIZE_INTEGRATION, + metadata: { + integration: integrationAuth.integration + } + }, + { + workspaceId: integrationAuth.workspace + } + ); return res.status(200).send({ integrationAuth @@ -530,6 +558,23 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => { integrationAuth: req.integrationAuth, accessToken: req.accessToken }); + + if (!integrationAuth) return res.status(400).send({ + message: "Failed to find integration authorization" + }); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.UNAUTHORIZE_INTEGRATION, + metadata: { + integration: integrationAuth.integration + } + }, + { + workspaceId: integrationAuth.workspace + } + ); return res.status(200).send({ integrationAuth diff --git a/backend/src/controllers/v1/integrationController.ts b/backend/src/controllers/v1/integrationController.ts index 91b32c805..e647cfb00 100644 --- a/backend/src/controllers/v1/integrationController.ts +++ b/backend/src/controllers/v1/integrationController.ts @@ -6,6 +6,8 @@ import { eventStartIntegration } from "../../events"; import Folder from "../../models/folder"; import { getFolderByPath } from "../../services/FolderService"; import { BadRequestError } from "../../utils/errors"; +import { EEAuditLogService } from "../../ee/services"; +import { EventType } from "../../ee/models"; /** * Create/initialize an (empty) integration for integration authorization @@ -74,6 +76,25 @@ export const createIntegration = async (req: Request, res: Response) => { }) }); } + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.CREATE_INTEGRATION, + metadata: { + integrationId: integration._id.toString(), + integration: integration.integration, + environment: integration.environment, + secretPath, + app: integration.app, + targetEnvironment: integration.targetEnvironment, + targetEnvironmentId: integration.targetEnvironmentId + } + }, + { + workspaceId: integration.workspace + } + ); return res.status(200).send({ integration @@ -148,8 +169,7 @@ export const updateIntegration = async (req: Request, res: Response) => { }; /** - * Delete integration with id [integrationId] and deactivate bot if there are - * no integrations left + * Delete integration with id [integrationId] * @param req * @param res * @returns @@ -163,6 +183,25 @@ export const deleteIntegration = async (req: Request, res: Response) => { if (!integration) throw new Error("Failed to find integration"); + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.DELETE_INTEGRATION, + metadata: { + integrationId: integration._id.toString(), + integration: integration.integration, + environment: integration.environment, + secretPath: integration.secretPath, + app: integration.app, + targetEnvironment: integration.targetEnvironment, + targetEnvironmentId: integration.targetEnvironmentId + } + }, + { + workspaceId: integration.workspace + } + ); + return res.status(200).send({ integration }); diff --git a/backend/src/controllers/v1/membershipController.ts b/backend/src/controllers/v1/membershipController.ts index d795bcb60..a473f0c4a 100644 --- a/backend/src/controllers/v1/membershipController.ts +++ b/backend/src/controllers/v1/membershipController.ts @@ -1,9 +1,12 @@ import { Request, Response } from "express"; -import { Key, Membership, MembershipOrg, User } from "../../models"; +import { Types } from "mongoose"; +import { Key, Membership, MembershipOrg, User, IUser } from "../../models"; +import { EventType } from "../../ee/models"; import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership"; import { sendMail } from "../../helpers/nodemailer"; import { ACCEPTED, ADMIN, MEMBER } from "../../variables"; import { getSiteURL } from "../../config"; +import { EEAuditLogService } from "../../ee/services"; /** * Check that user is a member of workspace with id [workspaceId] @@ -36,11 +39,11 @@ export const validateMembership = async (req: Request, res: Response) => { */ export const deleteMembership = async (req: Request, res: Response) => { const { membershipId } = req.params; - + // check if membership to delete exists const membershipToDelete = await Membership.findOne({ _id: membershipId - }).populate("user"); + }).populate<{ user: IUser }>("user"); if (!membershipToDelete) { throw new Error("Failed to delete workspace membership that doesn't exist"); @@ -66,6 +69,20 @@ export const deleteMembership = async (req: Request, res: Response) => { const deletedMembership = await deleteMember({ membershipId: membershipToDelete._id.toString() }); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.REMOVE_WORKSPACE_MEMBER, + metadata: { + userId: membershipToDelete.user._id.toString(), + email: membershipToDelete.user.email + } + }, + { + workspaceId: membership.workspace + } + ); return res.status(200).send({ deletedMembership @@ -140,7 +157,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => { const inviteeMembership = await Membership.findOne({ user: invitee._id, workspace: workspaceId - }); + }).populate<{ user: IUser }>("user"); if (inviteeMembership) throw new Error("Failed to add existing member of workspace"); @@ -181,6 +198,20 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => { } }); + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.ADD_WORKSPACE_MEMBER, + metadata: { + userId: invitee._id.toString(), + email: invitee.email + } + }, + { + workspaceId: new Types.ObjectId(workspaceId) + } + ); + return res.status(200).send({ invitee, latestKey diff --git a/backend/src/controllers/v1/secretsFolderController.ts b/backend/src/controllers/v1/secretsFolderController.ts index 6b31fd111..742852343 100644 --- a/backend/src/controllers/v1/secretsFolderController.ts +++ b/backend/src/controllers/v1/secretsFolderController.ts @@ -1,5 +1,6 @@ import { Request, Response } from "express"; import { Secret } from "../../models"; +import { Types } from "mongoose"; import Folder from "../../models/folder"; import { BadRequestError } from "../../utils/errors"; import { @@ -8,6 +9,7 @@ import { generateFolderId, getAllFolderIds, getFolderByPath, + getFolderPath, getParentFromFolderId, searchByFolderId, searchByFolderIdWithDir, @@ -15,10 +17,9 @@ import { } from "../../services/FolderService"; import { ADMIN, MEMBER } from "../../variables"; import { validateMembership } from "../../helpers/membership"; -import { FolderVersion } from "../../ee/models"; -import { EESecretService } from "../../ee/services"; +import { FolderVersion, EventType } from "../../ee/models"; +import { EESecretService, EEAuditLogService } from "../../ee/services"; -// TODO // verify workspace id/environment export const createFolder = async (req: Request, res: Response) => { const { workspaceId, environment, folderName, parentFolderId } = req.body; @@ -33,6 +34,7 @@ export const createFolder = async (req: Request, res: Response) => { environment, }).lean(); // space has no folders initialized + if (!folders) { const id = generateFolderId(); const folder = new Folder({ @@ -56,13 +58,32 @@ export const createFolder = async (req: Request, res: Response) => { workspaceId, environment, }); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.CREATE_FOLDER, + metadata: { + environment, + folderId: id, + folderName, + folderPath: `root/${folderName}` + } + }, + { + workspaceId: new Types.ObjectId(workspaceId) + } + ); + return res.json({ folder: { id, name: folderName } }); } const folder = appendFolder(folders.nodes, { folderName, parentFolderId }); + await Folder.findByIdAndUpdate(folders._id, folders); const parentFolder = searchByFolderId(folders.nodes, parentFolderId); + const folderVersion = new FolderVersion({ workspace: workspaceId, environment, @@ -75,6 +96,24 @@ export const createFolder = async (req: Request, res: Response) => { environment, folderId: parentFolderId, }); + + const folderPath = await getFolderPath(folders, folder.id); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.CREATE_FOLDER, + metadata: { + environment, + folderId: folder.id, + folderName, + folderPath + } + }, + { + workspaceId: new Types.ObjectId(workspaceId) + } + ); return res.json({ folder }); }; @@ -87,7 +126,7 @@ export const updateFolderById = async (req: Request, res: Response) => { if (!folders) { throw BadRequestError({ message: "The folder doesn't exist" }); } - + // check that user is a member of the workspace await validateMembership({ userId: req.user._id.toString(), @@ -100,10 +139,12 @@ export const updateFolderById = async (req: Request, res: Response) => { throw BadRequestError({ message: "The folder doesn't exist" }); } const folder = parentFolder.children.find(({ id }) => id === folderId); + if (!folder) { throw BadRequestError({ message: "The folder doesn't exist" }); } + const oldFolderName = folder.name; parentFolder.version += 1; folder.name = name; @@ -121,6 +162,25 @@ export const updateFolderById = async (req: Request, res: Response) => { folderId: parentFolder.id, }); + const folderPath = await getFolderPath(folders, folder.id); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.UPDATE_FOLDER, + metadata: { + environment, + folderId: folder.id, + oldFolderName, + newFolderName: name, + folderPath + } + }, + { + workspaceId: new Types.ObjectId(workspaceId) + } + ); + return res.json({ message: "Successfully updated folder", folder: { name: folder.name, id: folder.id }, @@ -143,6 +203,8 @@ export const deleteFolder = async (req: Request, res: Response) => { acceptedRoles: [ADMIN, MEMBER], }); + const folderPath = await getFolderPath(folders, folderId); + const delOp = deleteFolderById(folders.nodes, folderId); if (!delOp) { throw BadRequestError({ message: "The folder doesn't exist" }); @@ -173,6 +235,22 @@ export const deleteFolder = async (req: Request, res: Response) => { folderId: parentFolder.id, }); + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.DELETE_FOLDER , + metadata: { + environment, + folderId, + folderName: delFolder.name, + folderPath + } + }, + { + workspaceId: new Types.ObjectId(workspaceId) + } + ); + res.send({ message: "successfully deleted folders", folders: delFolderIds }); }; diff --git a/backend/src/controllers/v1/webhookController.ts b/backend/src/controllers/v1/webhookController.ts index afaf82e81..a79b794cf 100644 --- a/backend/src/controllers/v1/webhookController.ts +++ b/backend/src/controllers/v1/webhookController.ts @@ -4,7 +4,9 @@ import { client, getRootEncryptionKey } from "../../config"; import { validateMembership } from "../../helpers"; import Webhook from "../../models/webhooks"; import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService"; -import { BadRequestError } from "../../utils/errors"; +import { BadRequestError, ResourceNotFoundError } from "../../utils/errors"; +import { EEAuditLogService } from "../../ee/services"; +import { EventType } from "../../ee/models"; import { ADMIN, ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, MEMBER } from "../../variables"; export const createWebhook = async (req: Request, res: Response) => { @@ -27,6 +29,23 @@ export const createWebhook = async (req: Request, res: Response) => { } await webhook.save(); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.CREATE_WEBHOOK, + metadata: { + webhookId: webhook._id.toString(), + environment, + secretPath, + webhookUrl, + isDisabled: false + } + }, + { + workspaceId + } + ); return res.status(200).send({ webhook, @@ -54,6 +73,23 @@ export const updateWebhook = async (req: Request, res: Response) => { } await webhook.save(); + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.UPDATE_WEBHOOK_STATUS, + metadata: { + webhookId: webhook._id.toString(), + environment: webhook.environment, + secretPath: webhook.secretPath, + webhookUrl: webhook.url, + isDisabled + } + }, + { + workspaceId: webhook.workspace + } + ); + return res.status(200).send({ webhook, message: "successfully updated webhook" @@ -62,9 +98,10 @@ export const updateWebhook = async (req: Request, res: Response) => { export const deleteWebhook = async (req: Request, res: Response) => { const { webhookId } = req.params; - const webhook = await Webhook.findById(webhookId); + let webhook = await Webhook.findById(webhookId); + if (!webhook) { - throw BadRequestError({ message: "Webhook not found!!" }); + throw ResourceNotFoundError({ message: "Webhook not found!!" }); } await validateMembership({ @@ -72,8 +109,29 @@ export const deleteWebhook = async (req: Request, res: Response) => { workspaceId: webhook.workspace, acceptedRoles: [ADMIN, MEMBER] }); + + webhook = await Webhook.findByIdAndDelete(webhookId); - await webhook.deleteOne(); + if (!webhook) { + throw ResourceNotFoundError({ message: "Webhook not found!!" }); + } + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.DELETE_WEBHOOK, + metadata: { + webhookId: webhook._id.toString(), + environment: webhook.environment, + secretPath: webhook.secretPath, + webhookUrl: webhook.url, + isDisabled: webhook.isDisabled + } + }, + { + workspaceId: webhook.workspace + } + ); return res.status(200).send({ message: "successfully removed webhook" diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index e7de3a8cf..20680f1c6 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -8,8 +8,8 @@ import { ServiceTokenData, Workspace, } from "../../models"; -import { SecretVersion } from "../../ee/models"; -import { EELicenseService } from "../../ee/services"; +import { SecretVersion, EventType } from "../../ee/models"; +import { EELicenseService, EEAuditLogService } from "../../ee/services"; import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors"; import _ from "lodash"; import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables"; @@ -61,6 +61,20 @@ export const createWorkspaceEnvironment = async ( await EELicenseService.refreshPlan(workspace.organization, new Types.ObjectId(workspaceId)); + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.CREATE_ENVIRONMENT, + metadata: { + name: environmentName, + slug: environmentSlug + } + }, + { + workspaceId: workspace._id + } + ); + return res.status(200).send({ message: "Successfully created new environment", workspace: workspaceId, @@ -110,6 +124,8 @@ export const renameWorkspaceEnvironment = async ( if (envIndex === -1) { throw new Error("Invalid environment given"); } + + const oldEnvironment = workspace.environments[envIndex]; workspace.environments[envIndex].name = environmentName; workspace.environments[envIndex].slug = environmentSlug.toLowerCase(); @@ -142,8 +158,23 @@ export const renameWorkspaceEnvironment = async ( }, { $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } }, { arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] } - ) - + ); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.UPDATE_ENVIRONMENT, + metadata: { + oldName: oldEnvironment.name, + newName: environmentName, + oldSlug: oldEnvironment.slug, + newSlug: environmentSlug.toLowerCase() + } + }, + { + workspaceId: workspace._id + } + ); return res.status(200).send({ message: "Successfully update environment", @@ -179,6 +210,8 @@ export const deleteWorkspaceEnvironment = async ( if (envIndex === -1) { throw new Error("Invalid environment given"); } + + const oldEnvironment = workspace.environments[envIndex]; workspace.environments.splice(envIndex, 1); await workspace.save(); @@ -218,6 +251,20 @@ export const deleteWorkspaceEnvironment = async ( await EELicenseService.refreshPlan(workspace.organization, new Types.ObjectId(workspaceId)); + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.DELETE_ENVIRONMENT, + metadata: { + name: oldEnvironment.name, + slug: oldEnvironment.slug + } + }, + { + workspaceId: workspace._id + } + ); + return res.status(200).send({ message: "Successfully deleted environment", workspace: workspaceId, diff --git a/backend/src/controllers/v2/serviceTokenDataController.ts b/backend/src/controllers/v2/serviceTokenDataController.ts index 10fb50ef1..c918e6953 100644 --- a/backend/src/controllers/v2/serviceTokenDataController.ts +++ b/backend/src/controllers/v2/serviceTokenDataController.ts @@ -4,7 +4,8 @@ import bcrypt from "bcrypt"; import { ServiceTokenData } from "../../models"; import { getSaltRounds } from "../../config"; import { BadRequestError } from "../../utils/errors"; -import { ActorType } from "../../ee/models"; +import { ActorType, EventType } from "../../ee/models"; +import { EEAuditLogService } from "../../ee/services"; /** * Return service token data associated with service token on request @@ -99,6 +100,20 @@ export const createServiceTokenData = async (req: Request, res: Response) => { if (!serviceTokenData) throw new Error("Failed to find service token data"); const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`; + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.CREATE_SERVICE_TOKEN, + metadata: { + name, + scopes + } + }, + { + workspaceId + } + ); return res.status(200).send({ serviceToken, @@ -116,6 +131,24 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => { const { serviceTokenDataId } = req.params; const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId); + + if (!serviceTokenData) return res.status(200).send({ + message: "Failed to delete service token" + }); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.DELETE_SERVICE_TOKEN, + metadata: { + name: serviceTokenData.name, + scopes: serviceTokenData?.scopes + } + }, + { + workspaceId: serviceTokenData.workspace + } + ); return res.status(200).send({ serviceTokenData diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index a77cdca71..4db8c4855 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -13,13 +13,14 @@ import { ServiceActor, TFolderRootVersionSchema, TrustedIP, - UserActor + UserActor, + EventType } from "../../models"; import { EESecretService } from "../../services"; import { getLatestSecretVersionIds } from "../../helpers/secretVersion"; import Folder, { TFolderSchema } from "../../../models/folder"; import { searchByFolderId } from "../../../services/FolderService"; -import { EELicenseService } from "../../services"; +import { EELicenseService, EEAuditLogService } from "../../services"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; /** @@ -730,6 +731,21 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => { isActive, comment, }).save(); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.ADD_TRUSTED_IP, + metadata: { + trustedIpId: trustedIp._id.toString(), + ipAddress: trustedIp.ipAddress, + prefix: trustedIp.prefix + } + }, + { + workspaceId: trustedIp.workspace + } + ); return res.status(200).send({ trustedIp @@ -793,6 +809,25 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => { } ); + if (!trustedIp) return res.status(400).send({ + message: "Failed to update trusted IP" + }); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.UPDATE_TRUSTED_IP, + metadata: { + trustedIpId: trustedIp._id.toString(), + ipAddress: trustedIp.ipAddress, + prefix: trustedIp.prefix + } + }, + { + workspaceId: trustedIp.workspace + } + ); + return res.status(200).send({ trustedIp }); @@ -816,6 +851,25 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => { _id: new Types.ObjectId(trustedIpId), workspace: new Types.ObjectId(workspaceId) }); + + if (!trustedIp) return res.status(400).send({ + message: "Failed to delete trusted IP" + }); + + await EEAuditLogService.createAuditLog( + req.authData, + { + type: EventType.DELETE_TRUSTED_IP, + metadata: { + trustedIpId: trustedIp._id.toString(), + ipAddress: trustedIp.ipAddress, + prefix: trustedIp.prefix + } + }, + { + workspaceId: trustedIp.workspace + } + ); return res.status(200).send({ trustedIp diff --git a/backend/src/ee/models/auditLog/enums.ts b/backend/src/ee/models/auditLog/enums.ts index ce415e8c9..73dcd39bc 100644 --- a/backend/src/ee/models/auditLog/enums.ts +++ b/backend/src/ee/models/auditLog/enums.ts @@ -16,5 +16,25 @@ export enum EventType { REVEAL_SECRET = "reveal-secret", CREATE_SECRET = "create-secret", UPDATE_SECRET = "update-secret", - DELETE_SECRET = "delete-secret" + DELETE_SECRET = "delete-secret", + AUTHORIZE_INTEGRATION = "authorize-integration", + UNAUTHORIZE_INTEGRATION = "unauthorize-integration", + CREATE_INTEGRATION = "create-integration", + DELETE_INTEGRATION = "delete-integration", + ADD_TRUSTED_IP = "add-trusted-ip", + UPDATE_TRUSTED_IP = "update-trusted-ip", + DELETE_TRUSTED_IP = "delete-trusted-ip", + CREATE_SERVICE_TOKEN = "create-service-token", + DELETE_SERVICE_TOKEN = "delete-service-token", + CREATE_ENVIRONMENT = "create-environment", + UPDATE_ENVIRONMENT = "update-environment", + DELETE_ENVIRONMENT = "delete-environment", + ADD_WORKSPACE_MEMBER = "add-workspace-member", + REMOVE_WORKSPACE_MEMBER = "remove-workspace-member", + CREATE_FOLDER = "create-folder", + UPDATE_FOLDER = "update-folder", + DELETE_FOLDER = "delete-folder", + CREATE_WEBHOOK = "create-webhook", + UPDATE_WEBHOOK_STATUS = "update-webhook-status", + DELETE_WEBHOOK = "delete-webhook" } \ No newline at end of file diff --git a/backend/src/ee/models/auditLog/types.ts b/backend/src/ee/models/auditLog/types.ts index 848fb88db..04ff317e6 100644 --- a/backend/src/ee/models/auditLog/types.ts +++ b/backend/src/ee/models/auditLog/types.ts @@ -80,9 +80,224 @@ interface DeleteSecretEvent { } } +interface AuthorizeIntegrationEvent { + type: EventType.AUTHORIZE_INTEGRATION; + metadata: { + integration: string; // TODO: fix type + } +} + +interface UnauthorizeIntegrationEvent { + type: EventType.UNAUTHORIZE_INTEGRATION; + metadata: { + integration: string; // TODO: fix type + } +} + +interface CreateIntegrationEvent { + type: EventType.CREATE_INTEGRATION; + metadata: { + integrationId: string; + integration: string; // TODO: fix type + environment: string; + secretPath: string; + app?: string; + targetEnvironment?: string; + targetEnvironmentId?: string; // TODO: consider adding other vars + } +} + +interface DeleteIntegrationEvent { + type: EventType.DELETE_INTEGRATION; + metadata: { + integrationId: string; + integration: string; // TODO: fix type + environment: string; + secretPath: string; + app?: string; + targetEnvironment?: string; + targetEnvironmentId?: string; + } +} + +interface AddTrustedIPEvent { + type: EventType.ADD_TRUSTED_IP; + metadata: { + trustedIpId: string; + ipAddress: string; + prefix?: number; + } +} + +interface UpdateTrustedIPEvent { + type: EventType.UPDATE_TRUSTED_IP; + metadata: { + trustedIpId: string; + ipAddress: string; + prefix?: number; + } +} + +interface DeleteTrustedIPEvent { + type: EventType.DELETE_TRUSTED_IP; + metadata: { + trustedIpId: string; + ipAddress: string; + prefix?: number; + } +} + +interface CreateServiceTokenEvent { + type: EventType.CREATE_SERVICE_TOKEN; + metadata: { + name: string; + scopes: Array<{ + environment: string; + secretPath: string; + }>; + } +} + +interface DeleteServiceTokenEvent { + type: EventType.DELETE_SERVICE_TOKEN; + metadata: { + name: string; + scopes: Array<{ + environment: string; + secretPath: string; + }>; + } +} + +interface CreateEnvironmentEvent { + type: EventType.CREATE_ENVIRONMENT; + metadata: { + name: string; + slug: string; + } +} + +interface UpdateEnvironmentEvent { + type: EventType.UPDATE_ENVIRONMENT; + metadata: { + oldName: string; + newName: string; + oldSlug: string; + newSlug: string; + } +} + +interface DeleteEnvironmentEvent { + type: EventType.DELETE_ENVIRONMENT; + metadata: { + name: string; + slug: string; + } +} + +interface AddWorkspaceMemberEvent { + type: EventType.ADD_WORKSPACE_MEMBER; + metadata: { + userId: string; + email: string; + } +} + +interface RemoveWorkspaceMemberEvent { + type: EventType.REMOVE_WORKSPACE_MEMBER; + metadata: { + userId: string; + email: string; + } +} + +interface CreateFolderEvent { + type: EventType.CREATE_FOLDER; + metadata: { + environment: string; + folderId: string; + folderName: string; + folderPath: string; + } +} + +interface UpdateFolderEvent { + type: EventType.UPDATE_FOLDER; + metadata: { + environment: string; + folderId: string; + oldFolderName: string; + newFolderName: string; + folderPath: string; + } +} + +interface DeleteFolderEvent { + type: EventType.DELETE_FOLDER; + metadata: { + environment: string; + folderId: string; + folderName: string; + folderPath: string; + } +} + +interface CreateWebhookEvent { + type: EventType.CREATE_WEBHOOK, + metadata: { + webhookId: string; + environment: string; + secretPath: string; + webhookUrl: string; + isDisabled: boolean; + } +} + +interface UpdateWebhookStatusEvent { + type: EventType.UPDATE_WEBHOOK_STATUS, + metadata: { + webhookId: string; + environment: string; + secretPath: string; + webhookUrl: string; + isDisabled: boolean; + } +} + +interface DeleteWebhookEvent { + type: EventType.DELETE_WEBHOOK, + metadata: { + webhookId: string; + environment: string; + secretPath: string; + webhookUrl: string; + isDisabled: boolean; + } +} + export type Event = | GetSecretsEvent | GetSecretEvent | CreateSecretEvent | UpdateSecretEvent - | DeleteSecretEvent; \ No newline at end of file + | DeleteSecretEvent + | AuthorizeIntegrationEvent + | UnauthorizeIntegrationEvent + | CreateIntegrationEvent + | DeleteIntegrationEvent + | AddTrustedIPEvent + | UpdateTrustedIPEvent + | DeleteTrustedIPEvent + | CreateServiceTokenEvent + | DeleteServiceTokenEvent + | CreateEnvironmentEvent + | UpdateEnvironmentEvent + | DeleteEnvironmentEvent + | AddWorkspaceMemberEvent + | RemoveWorkspaceMemberEvent + | CreateFolderEvent + | UpdateFolderEvent + | DeleteFolderEvent + | CreateWebhookEvent + | UpdateWebhookStatusEvent + | DeleteWebhookEvent; \ No newline at end of file diff --git a/backend/src/ee/routes/v1/workspace.ts b/backend/src/ee/routes/v1/workspace.ts index 90eea76e6..2143f0781 100644 --- a/backend/src/ee/routes/v1/workspace.ts +++ b/backend/src/ee/routes/v1/workspace.ts @@ -97,8 +97,8 @@ router.get( query("eventType").isString().isIn(Object.values(EventType)).optional({ nullable: true }), query("userAgentType").isString().isIn(Object.values(UserAgentType)).optional({ nullable: true }), query("actor").isString().optional({ nullable: true }), - query("offset").isString().default("0"), - query("limit").isString().default("20"), + query("offset").default("0"), + query("limit").default("20"), validateRequest, workspaceController.getWorkspaceAuditLogs ); diff --git a/backend/src/ee/services/EELicenseService.ts b/backend/src/ee/services/EELicenseService.ts index de8e4369d..569f2483f 100644 --- a/backend/src/ee/services/EELicenseService.ts +++ b/backend/src/ee/services/EELicenseService.ts @@ -63,12 +63,12 @@ class EELicenseService { environmentsUsed: 0, secretVersioning: true, pitRecovery: false, - ipAllowlisting: false, + ipAllowlisting: true, rbac: true, customRateLimits: true, customAlerts: true, - auditLogs: false, - auditLogsRetentionDays: 0, + auditLogs: true, + auditLogsRetentionDays: 30, samlSSO: false, status: null, trial_end: null, diff --git a/backend/src/services/FolderService.ts b/backend/src/services/FolderService.ts index f2dfb3050..7d3e4bd59 100644 --- a/backend/src/services/FolderService.ts +++ b/backend/src/services/FolderService.ts @@ -1,6 +1,7 @@ import { nanoid } from "nanoid"; import { Types } from "mongoose"; -import Folder, { TFolderSchema } from "../models/folder"; +import Folder, { TFolderSchema, TFolderRootSchema } from "../models/folder"; +import { ResourceNotFoundError } from "../utils/errors"; type TAppendFolderDTO = { folderName: string; @@ -172,6 +173,20 @@ export const searchByFolderIdWithDir = ( return; }; +export const getFolderPath = ( + folders: TFolderRootSchema, + folderId: string + ) => { + const folderBySearch = searchByFolderIdWithDir(folders.nodes, folderId); + + if (!folderBySearch) throw ResourceNotFoundError({ + message: "Failed to find folder" + }); + + const folderPath = folderBySearch.dir.map((folder) => folder.name).join("/"); + return folderPath; + } + // to get folder of a path given // Like /frontend/folder#1 export const getFolderByPath = (folders: TFolderSchema, searchPath: string) => { diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx index f54b20b1a..3b15efa91 100644 --- a/frontend/src/hooks/api/auditLogs/constants.tsx +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -1,11 +1,31 @@ import { EventType, UserAgentType } from "./enums"; export const eventToNameMap: { [K in EventType]: string } = { - [EventType.GET_SECRETS]: "Get Secrets", - [EventType.GET_SECRET]: "Get Secret", - [EventType.CREATE_SECRET]: "Create Secret", - [EventType.UPDATE_SECRET]: "Update Secret", - [EventType.DELETE_SECRET]: "Delete Secret", + [EventType.GET_SECRETS]: "Get secrets", + [EventType.GET_SECRET]: "Get secret", + [EventType.CREATE_SECRET]: "Create secret", + [EventType.UPDATE_SECRET]: "Update secret", + [EventType.DELETE_SECRET]: "Delete secret", + [EventType.AUTHORIZE_INTEGRATION]: "Authorize integration", + [EventType.UNAUTHORIZE_INTEGRATION]: "Unauthorize integration", + [EventType.CREATE_INTEGRATION]: "Create integration", + [EventType.DELETE_INTEGRATION]: "Delete integration", + [EventType.ADD_TRUSTED_IP]: "Add trusted IP", + [EventType.UPDATE_TRUSTED_IP]: "Update trusted IP", + [EventType.DELETE_TRUSTED_IP]: "Delete trusted IP", + [EventType.CREATE_SERVICE_TOKEN]: "Create service token", + [EventType.DELETE_SERVICE_TOKEN]: "Delete service token", + [EventType.CREATE_ENVIRONMENT]: "Create environment", + [EventType.UPDATE_ENVIRONMENT]: "Update environment", + [EventType.DELETE_ENVIRONMENT]: "Delete environment", + [EventType.ADD_WORKSPACE_MEMBER]: "Add member", + [EventType.REMOVE_WORKSPACE_MEMBER]: "Remove member", + [EventType.CREATE_FOLDER]: "Create folder", + [EventType.UPDATE_FOLDER]: "Update folder", + [EventType.DELETE_FOLDER]: "Delete folder", + [EventType.CREATE_WEBHOOK]: "Create webhook", + [EventType.UPDATE_WEBHOOK_STATUS]: "Update webhook status", + [EventType.DELETE_WEBHOOK]: "Delete webhook", }; export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = { diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx index 4c75f6395..3e159552d 100644 --- a/frontend/src/hooks/api/auditLogs/enums.tsx +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -11,9 +11,29 @@ export enum UserAgentType { } export enum EventType { - GET_SECRETS = "get-secrets", - GET_SECRET = "get-secret", - CREATE_SECRET = "create-secret", - UPDATE_SECRET = "update-secret", - DELETE_SECRET = "delete-secret" + GET_SECRETS = "get-secrets", + GET_SECRET = "get-secret", + CREATE_SECRET = "create-secret", + UPDATE_SECRET = "update-secret", + DELETE_SECRET = "delete-secret", + AUTHORIZE_INTEGRATION = "authorize-integration", + UNAUTHORIZE_INTEGRATION = "unauthorize-integration", + CREATE_INTEGRATION = "create-integration", + DELETE_INTEGRATION = "delete-integration", + ADD_TRUSTED_IP = "add-trusted-ip", + UPDATE_TRUSTED_IP = "update-trusted-ip", + DELETE_TRUSTED_IP = "delete-trusted-ip", + CREATE_SERVICE_TOKEN = "create-service-token", + DELETE_SERVICE_TOKEN = "delete-service-token", + CREATE_ENVIRONMENT = "create-environment", + UPDATE_ENVIRONMENT = "update-environment", + DELETE_ENVIRONMENT = "delete-environment", + ADD_WORKSPACE_MEMBER = "add-workspace-member", + REMOVE_WORKSPACE_MEMBER = "remove-workspace-member", + CREATE_FOLDER = "create-folder", + UPDATE_FOLDER = "update-folder", + DELETE_FOLDER = "delete-folder", + CREATE_WEBHOOK = "create-webhook", + UPDATE_WEBHOOK_STATUS = "update-webhook-status", + DELETE_WEBHOOK = "delete-webhook" } \ No newline at end of file diff --git a/frontend/src/hooks/api/auditLogs/queries.tsx b/frontend/src/hooks/api/auditLogs/queries.tsx index 05148c941..930de7f0a 100644 --- a/frontend/src/hooks/api/auditLogs/queries.tsx +++ b/frontend/src/hooks/api/auditLogs/queries.tsx @@ -2,28 +2,22 @@ import { useQuery } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { EventType, UserAgentType } from "./enums"; import { Actor, - AuditLog} from "./types"; + AuditLog, + AuditLogFilters +} from "./types"; export const workspaceKeys = { - getAuditLogs: (workspaceId: string, filters: { - eventType?: EventType; - userAgentType?: UserAgentType; - actor?: string; - }) => [{ workspaceId, filters }, "audit-logs"] as const, + getAuditLogs: (workspaceId: string, filters: AuditLogFilters) => [{ workspaceId, filters }, "audit-logs"] as const, getAuditLogActorFilterOpts: (workspaceId: string) => [{ workspaceId }, "audit-log-actor-filters"] as const } -export const useGetAuditLogs = (workspaceId: string, filters: { - eventType?: EventType; - userAgentType?: UserAgentType; - actor?: string; -}) => { +export const useGetAuditLogs = (workspaceId: string, filters: AuditLogFilters) => { return useQuery({ queryKey: workspaceKeys.getAuditLogs(workspaceId, filters), queryFn: async () => { + const params = new URLSearchParams(); if (filters.eventType) { params.append("eventType", filters.eventType); @@ -37,6 +31,13 @@ export const useGetAuditLogs = (workspaceId: string, filters: { params.append("actor", filters.actor); } + if (filters.actor) { + params.append("actor", filters.actor); + } + + params.append("offset ", String(filters.offset)); + params.append("limit ", String(filters.limit)); + const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>(`/api/v1/workspace/${workspaceId}/audit-logs`, { params }); return data.auditLogs; } diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index e836521ec..b7c3cc47a 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -82,12 +82,227 @@ interface DeleteSecretEvent { } } +interface AuthorizeIntegrationEvent { + type: EventType.AUTHORIZE_INTEGRATION; + metadata: { + integration: string; // TODO: fix type + } +} + +interface UnauthorizeIntegrationEvent { + type: EventType.UNAUTHORIZE_INTEGRATION; + metadata: { + integration: string; // TODO: fix type + } +} + +interface CreateIntegrationEvent { + type: EventType.CREATE_INTEGRATION; + metadata: { + integrationId: string; + integration: string; // TODO: fix type + environment: string; + secretPath: string; + app?: string; + targetEnvironment?: string; + targetEnvironmentId?: string; // TODO: consider adding other vars + } +} + +interface DeleteIntegrationEvent { + type: EventType.DELETE_INTEGRATION; + metadata: { + integrationId: string; + integration: string; // TODO: fix type + environment: string; + secretPath: string; + app?: string; + targetEnvironment?: string; + targetEnvironmentId?: string; + } +} + +interface AddTrustedIPEvent { + type: EventType.ADD_TRUSTED_IP; + metadata: { + trustedIpId: string; + ipAddress: string; + prefix?: number; + } +} + +interface UpdateTrustedIPEvent { + type: EventType.UPDATE_TRUSTED_IP; + metadata: { + trustedIpId: string; + ipAddress: string; + prefix?: number; + } +} + +interface DeleteTrustedIPEvent { + type: EventType.DELETE_TRUSTED_IP; + metadata: { + trustedIpId: string; + ipAddress: string; + prefix?: number; + } +} + +interface CreateServiceTokenEvent { + type: EventType.CREATE_SERVICE_TOKEN; + metadata: { + name: string; + scopes: Array<{ + environment: string; + secretPath: string; + }>; + } +} + +interface DeleteServiceTokenEvent { + type: EventType.DELETE_SERVICE_TOKEN; + metadata: { + name: string; + scopes: Array<{ + environment: string; + secretPath: string; + }>; + } +} + +interface CreateEnvironmentEvent { + type: EventType.CREATE_ENVIRONMENT; + metadata: { + name: string; + slug: string; + } +} + +interface UpdateEnvironmentEvent { + type: EventType.UPDATE_ENVIRONMENT; + metadata: { + oldName: string; + newName: string; + oldSlug: string; + newSlug: string; + } +} + +interface DeleteEnvironmentEvent { + type: EventType.DELETE_ENVIRONMENT; + metadata: { + name: string; + slug: string; + } +} + +interface AddWorkspaceMemberEvent { + type: EventType.ADD_WORKSPACE_MEMBER; + metadata: { + userId: string; + email: string; + } +} + +interface RemoveWorkspaceMemberEvent { + type: EventType.REMOVE_WORKSPACE_MEMBER; + metadata: { + userId: string; + email: string; + } +} + +interface CreateFolderEvent { + type: EventType.CREATE_FOLDER; + metadata: { + environment: string; + folderId: string; + folderName: string; + folderPath: string; + } +} + +interface UpdateFolderEvent { + type: EventType.UPDATE_FOLDER; + metadata: { + environment: string; + folderId: string; + oldFolderName: string; + newFolderName: string; + folderPath: string; + } +} + +interface DeleteFolderEvent { + type: EventType.DELETE_FOLDER; + metadata: { + environment: string; + folderId: string; + folderName: string; + folderPath: string; + } +} + +interface CreateWebhookEvent { + type: EventType.CREATE_WEBHOOK, + metadata: { + webhookId: string; + environment: string; + secretPath: string; + webhookUrl: string; + isDisabled: boolean; + } +} + +interface UpdateWebhookStatusEvent { + type: EventType.UPDATE_WEBHOOK_STATUS, + metadata: { + webhookId: string; + environment: string; + secretPath: string; + webhookUrl: string; + isDisabled: boolean; + } +} + +interface DeleteWebhookEvent { + type: EventType.DELETE_WEBHOOK, + metadata: { + webhookId: string; + environment: string; + secretPath: string; + webhookUrl: string; + isDisabled: boolean; + } +} + export type Event = | GetSecretsEvent | GetSecretEvent | CreateSecretEvent | UpdateSecretEvent - | DeleteSecretEvent; + | DeleteSecretEvent + | AuthorizeIntegrationEvent + | UnauthorizeIntegrationEvent + | CreateIntegrationEvent + | DeleteIntegrationEvent + | AddTrustedIPEvent + | UpdateTrustedIPEvent + | DeleteTrustedIPEvent + | CreateServiceTokenEvent + | DeleteServiceTokenEvent + | CreateEnvironmentEvent + | UpdateEnvironmentEvent + | DeleteEnvironmentEvent + | AddWorkspaceMemberEvent + | RemoveWorkspaceMemberEvent + | CreateFolderEvent + | UpdateFolderEvent + | DeleteFolderEvent + | CreateWebhookEvent + | UpdateWebhookStatusEvent + | DeleteWebhookEvent; export type AuditLog = { _id: string; @@ -101,3 +316,11 @@ export type AuditLog = { createdAt: string; updatedAt: string; } + +export type AuditLogFilters = { + eventType?: EventType; + userAgentType?: UserAgentType; + actor?: string; + offset: number; + limit: number; +} \ No newline at end of file diff --git a/frontend/src/views/Project/LogsPage/components/LogsTable.tsx b/frontend/src/views/Project/LogsPage/components/LogsTable.tsx index 823e3f56d..0262a640d 100644 --- a/frontend/src/views/Project/LogsPage/components/LogsTable.tsx +++ b/frontend/src/views/Project/LogsPage/components/LogsTable.tsx @@ -33,7 +33,9 @@ export const LogsTable = ({ const { data, isLoading } = useGetAuditLogs(currentWorkspace?._id ?? "", { eventType, userAgentType, - actor + actor, + offset: 0, // TODO: update with pagination + limit: 20 // TODO: update with pagination }); return ( diff --git a/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx b/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx index 05dfec9b0..7c4ecf90d 100644 --- a/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx +++ b/frontend/src/views/Project/LogsPage/components/LogsTableRow.tsx @@ -79,6 +79,169 @@ export const LogsTableRow = ({

{`Secret: ${event.metadata.secretKey}`}

); + case EventType.AUTHORIZE_INTEGRATION: + return ( + +

{`Integration: ${event.metadata.integration}`}

+ + ); + case EventType.UNAUTHORIZE_INTEGRATION: + return ( + +

{`Integration: ${event.metadata.integration}`}

+ + ); + case EventType.CREATE_INTEGRATION: + return ( + +

{`Integration: ${event.metadata.integration}`}

+

{`Environment: ${event.metadata.environment}`}

+

{`Path: ${event.metadata.secretPath}`}

+ {event.metadata.app && ( +

{`Target app: ${event.metadata.app}`}

+ )} + {event.metadata.targetEnvironment && ( +

{`Target environment: ${event.metadata.targetEnvironment}`}

+ )} + {event.metadata.targetEnvironmentId && ( +

{`Target environment ID: ${event.metadata.targetEnvironmentId}`}

+ )} + + ); + case EventType.DELETE_INTEGRATION: + return ( + +

{`Integration: ${event.metadata.integration}`}

+

{`Environment: ${event.metadata.environment}`}

+

{`Path: ${event.metadata.secretPath}`}

+ {event.metadata.app && ( +

{`Target App: ${event.metadata.app}`}

+ )} + {event.metadata.targetEnvironment && ( +

{`Target environment: ${event.metadata.targetEnvironment}`}

+ )} + {event.metadata.targetEnvironmentId && ( +

{`Target environment ID: ${event.metadata.targetEnvironmentId}`}

+ )} + + ); + case EventType.ADD_TRUSTED_IP: + return ( + +

{`IP: ${event.metadata.ipAddress}${event.metadata.prefix !== undefined ? `/${event.metadata.prefix}` : ""}`}

+ + ); + case EventType.UPDATE_TRUSTED_IP: + return ( + +

{`IP: ${event.metadata.ipAddress}${event.metadata.prefix !== undefined ? `/${event.metadata.prefix}` : ""}`}

+ + ); + case EventType.DELETE_TRUSTED_IP: + return ( + +

{`IP: ${event.metadata.ipAddress}${event.metadata.prefix !== undefined ? `/${event.metadata.prefix}` : ""}`}

+ + ); + case EventType.CREATE_SERVICE_TOKEN: + return ( + +

{`Name: ${event.metadata.name}`}

+ + ); + case EventType.DELETE_SERVICE_TOKEN: + return ( + +

{`Name: ${event.metadata.name}`}

+ + ); + case EventType.CREATE_ENVIRONMENT: + return ( + +

{`Name: ${event.metadata.name}`}

+

{`Slug: ${event.metadata.slug}`}

+ + ); + case EventType.UPDATE_ENVIRONMENT: + return ( + +

{`Old name: ${event.metadata.oldName}`}

+

{`New name: ${event.metadata.newName}`}

+

{`Old slug: ${event.metadata.oldSlug}`}

+

{`New slug: ${event.metadata.newSlug}`}

+ + ); + case EventType.DELETE_ENVIRONMENT: + return ( + +

{`Name: ${event.metadata.name}`}

+

{`Slug: ${event.metadata.slug}`}

+ + ); + case EventType.ADD_WORKSPACE_MEMBER: + return ( + +

{`Email: ${event.metadata.email}`}

+ + ); + case EventType.REMOVE_WORKSPACE_MEMBER: + return ( + +

{`Email: ${event.metadata.email}`}

+ + ); + case EventType.CREATE_FOLDER: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Path: ${event.metadata.folderPath}`}

+

{`Folder: ${event.metadata.folderName}`}

+ + ); + case EventType.UPDATE_FOLDER: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Path: ${event.metadata.folderPath}`}

+

{`Old folder: ${event.metadata.oldFolderName}`}

+

{`New folder: ${event.metadata.newFolderName}`}

+ + ); + case EventType.DELETE_FOLDER: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Path: ${event.metadata.folderPath}`}

+

{`Folder: ${event.metadata.folderName}`}

+ + ); + case EventType.CREATE_WEBHOOK: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Secret Path: ${event.metadata.secretPath}`}

+

{`Webhook URL: ${event.metadata.webhookUrl}`}

+

{`Disabled: ${event.metadata.isDisabled}`}

+ + ); + case EventType.UPDATE_WEBHOOK_STATUS: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Secret Path: ${event.metadata.secretPath}`}

+

{`Webhook URL: ${event.metadata.webhookUrl}`}

+

{`Disabled: ${event.metadata.isDisabled}`}

+ + ); + case EventType.DELETE_WEBHOOK: + return ( + +

{`Environment: ${event.metadata.environment}`}

+

{`Secret Path: ${event.metadata.secretPath}`}

+

{`Webhook URL: ${event.metadata.webhookUrl}`}

+

{`Disabled: ${event.metadata.isDisabled}`}

+ + ); default: return ( Test From 6cb8cf53f838ca77e89ee93f8588d8d1707d18fa Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 8 Aug 2023 12:52:34 +0700 Subject: [PATCH 5/9] Add date filter and pagination component to audit log v2 --- .../controllers/v1/membershipController.ts | 2 +- .../controllers/v1/secretsFolderController.ts | 4 +- .../controllers/v2/environmentController.ts | 4 +- .../ee/controllers/v1/workspaceController.ts | 28 ++- backend/src/ee/routes/v1/workspace.ts | 8 +- backend/src/services/FolderService.ts | 2 +- .../components/v2/Pagination/Pagination.tsx | 2 +- frontend/src/hooks/api/auditLogs/queries.tsx | 16 +- frontend/src/hooks/api/auditLogs/types.tsx | 2 + .../LogsPage/components/LogsFilter.tsx | 182 ++++++++++++------ .../LogsPage/components/LogsSection.tsx | 24 ++- .../Project/LogsPage/components/LogsTable.tsx | 49 +++-- .../Project/LogsPage/components/types.tsx | 17 +- 13 files changed, 235 insertions(+), 105 deletions(-) diff --git a/backend/src/controllers/v1/membershipController.ts b/backend/src/controllers/v1/membershipController.ts index a473f0c4a..48689be1c 100644 --- a/backend/src/controllers/v1/membershipController.ts +++ b/backend/src/controllers/v1/membershipController.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; import { Types } from "mongoose"; -import { Key, Membership, MembershipOrg, User, IUser } from "../../models"; +import { IUser, Key, Membership, MembershipOrg, User } from "../../models"; import { EventType } from "../../ee/models"; import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership"; import { sendMail } from "../../helpers/nodemailer"; diff --git a/backend/src/controllers/v1/secretsFolderController.ts b/backend/src/controllers/v1/secretsFolderController.ts index 742852343..334df6d4f 100644 --- a/backend/src/controllers/v1/secretsFolderController.ts +++ b/backend/src/controllers/v1/secretsFolderController.ts @@ -17,8 +17,8 @@ import { } from "../../services/FolderService"; import { ADMIN, MEMBER } from "../../variables"; import { validateMembership } from "../../helpers/membership"; -import { FolderVersion, EventType } from "../../ee/models"; -import { EESecretService, EEAuditLogService } from "../../ee/services"; +import { EventType, FolderVersion } from "../../ee/models"; +import { EEAuditLogService, EESecretService } from "../../ee/services"; // verify workspace id/environment export const createFolder = async (req: Request, res: Response) => { diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index 20680f1c6..8cbf1a229 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -8,8 +8,8 @@ import { ServiceTokenData, Workspace, } from "../../models"; -import { SecretVersion, EventType } from "../../ee/models"; -import { EELicenseService, EEAuditLogService } from "../../ee/services"; +import { EventType, SecretVersion } from "../../ee/models"; +import { EEAuditLogService, EELicenseService } from "../../ee/services"; import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors"; import _ from "lodash"; import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables"; diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index 4db8c4855..9354c4f13 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -4,6 +4,7 @@ import { Membership, Secret, ServiceTokenData, User } from "../../../models"; import { ActorType, AuditLog, + EventType, FolderVersion, IPType, ISecretVersion, @@ -13,14 +14,13 @@ import { ServiceActor, TFolderRootVersionSchema, TrustedIP, - UserActor, - EventType + UserActor } from "../../models"; import { EESecretService } from "../../services"; import { getLatestSecretVersionIds } from "../../helpers/secretVersion"; import Folder, { TFolderSchema } from "../../../models/folder"; import { searchByFolderId } from "../../../services/FolderService"; -import { EELicenseService, EEAuditLogService } from "../../services"; +import { EEAuditLogService, EELicenseService } from "../../services"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; /** @@ -611,7 +611,10 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { const offset: number = parseInt(req.query.offset as string); const limit: number = parseInt(req.query.limit as string); - const auditLogs = await AuditLog.find({ + const startDate = req.query.startDate as string; + const endDate = req.query.endDate as string; + + const query = { workspace: new Types.ObjectId(workspaceId), ...(eventType ? { "event.type": eventType @@ -626,14 +629,25 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { } : { "actor.metadata.serviceId": actor.split("-", 2)[1] }) + } : {}), + ...(startDate || endDate ? { + createdAt: { + ...(startDate && { $gte: new Date(startDate) }), + ...(endDate && { $lte: new Date(endDate) }) + } } : {}) - }) + } + + const auditLogs = await AuditLog.find(query) .sort({ createdAt: -1 }) .skip(offset) .limit(limit); - + + const totalCount = await AuditLog.countDocuments(query); + return res.status(200).send({ - auditLogs + auditLogs, + totalCount }); } diff --git a/backend/src/ee/routes/v1/workspace.ts b/backend/src/ee/routes/v1/workspace.ts index 2143f0781..529ba0550 100644 --- a/backend/src/ee/routes/v1/workspace.ts +++ b/backend/src/ee/routes/v1/workspace.ts @@ -96,9 +96,11 @@ router.get( param("workspaceId").exists().trim(), query("eventType").isString().isIn(Object.values(EventType)).optional({ nullable: true }), query("userAgentType").isString().isIn(Object.values(UserAgentType)).optional({ nullable: true }), - query("actor").isString().optional({ nullable: true }), - query("offset").default("0"), - query("limit").default("20"), + query("actor").optional({ nullable: true }), + query("startDate").isISO8601().withMessage("Invalid start date format").optional({ nullable: true }), + query("endDate").isISO8601().withMessage("Invalid end date format").optional({ nullable: true }), + query("offset"), + query("limit"), validateRequest, workspaceController.getWorkspaceAuditLogs ); diff --git a/backend/src/services/FolderService.ts b/backend/src/services/FolderService.ts index 7d3e4bd59..8e1c00750 100644 --- a/backend/src/services/FolderService.ts +++ b/backend/src/services/FolderService.ts @@ -1,6 +1,6 @@ import { nanoid } from "nanoid"; import { Types } from "mongoose"; -import Folder, { TFolderSchema, TFolderRootSchema } from "../models/folder"; +import Folder, { TFolderRootSchema, TFolderSchema } from "../models/folder"; import { ResourceNotFoundError } from "../utils/errors"; type TAppendFolderDTO = { diff --git a/frontend/src/components/v2/Pagination/Pagination.tsx b/frontend/src/components/v2/Pagination/Pagination.tsx index 99d50a07f..f4a75eac7 100644 --- a/frontend/src/components/v2/Pagination/Pagination.tsx +++ b/frontend/src/components/v2/Pagination/Pagination.tsx @@ -21,7 +21,7 @@ export type PaginationProps = { perPage?: number; onChangePage: (pageNumber: number) => void; onChangePerPage: (newRows: number) => void; - className: string; + className?: string; perPageList?: number[]; }; diff --git a/frontend/src/hooks/api/auditLogs/queries.tsx b/frontend/src/hooks/api/auditLogs/queries.tsx index 930de7f0a..94df8bf8b 100644 --- a/frontend/src/hooks/api/auditLogs/queries.tsx +++ b/frontend/src/hooks/api/auditLogs/queries.tsx @@ -31,15 +31,19 @@ export const useGetAuditLogs = (workspaceId: string, filters: AuditLogFilters) = params.append("actor", filters.actor); } - if (filters.actor) { - params.append("actor", filters.actor); + if (filters.startDate) { + params.append("startDate", filters.startDate.toISOString()); } - params.append("offset ", String(filters.offset)); - params.append("limit ", String(filters.limit)); + if (filters.endDate) { + params.append("endDate", filters.endDate.toISOString()); + } + + params.append("offset", String(filters.offset)); + params.append("limit", String(filters.limit)); - const { data } = await apiRequest.get<{ auditLogs: AuditLog[] }>(`/api/v1/workspace/${workspaceId}/audit-logs`, { params }); - return data.auditLogs; + const { data } = await apiRequest.get<{ auditLogs: AuditLog[], totalCount: number }>(`/api/v1/workspace/${workspaceId}/audit-logs`, { params }); + return data; } }); } diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index b7c3cc47a..71cf01c5c 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -323,4 +323,6 @@ export type AuditLogFilters = { actor?: string; offset: number; limit: number; + startDate?: Date; + endDate?: Date; } \ No newline at end of file diff --git a/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx b/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx index d1273f3aa..dce7c4938 100644 --- a/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx +++ b/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx @@ -1,18 +1,19 @@ +import { useState } from "react"; import { Control, Controller, UseFormReset } from "react-hook-form"; import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button, + DatePicker, FormControl, Select, SelectItem} from "@app/components/v2"; import { useWorkspace } from "@app/context"; import { useGetAuditLogActorFilterOpts } from "@app/hooks/api"; - -import { eventToNameMap, userAgentTTypeoNameMap } from "~/hooks/api/auditLogs/constants"; -import { ActorType } from "~/hooks/api/auditLogs/enums"; -import { Actor } from "~/hooks/api/auditLogs/types"; +import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants"; +import { ActorType } from "@app/hooks/api/auditLogs/enums"; +import { Actor } from "@app/hooks/api/auditLogs/types"; import { AuditLogFilterFormData } from "./types"; @@ -28,6 +29,9 @@ export const LogsFilter = ({ control, reset }: Props) => { + const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false); + const [isEndDatePickerOpen, setIsEndDatePickerOpen] = useState(false); + const { currentWorkspace } = useWorkspace(); const { data, isLoading } = useGetAuditLogActorFilterOpts(currentWorkspace?._id ?? ""); @@ -58,65 +62,41 @@ export const LogsFilter = ({ return (
-
- ( - ( + + onChange(e)} - className="w-full" - > - {eventTypes.map(({ label, value }) => ( - - {label} - - ))} - - - )} - /> -
+ {eventTypes.map(({ label, value }) => ( + + {label} + + ))} + + + )} + /> {!isLoading && data && data.length > 0 && ( -
- ( - - - - )} - /> -
- )} -
( )} /> -
+ )} + ( + + + + )} + /> + { + return ( + + { + onChange(date); + setIsStartDatePickerOpen(false); + }} + popUpProps={{ + open: isStartDatePickerOpen, + onOpenChange: setIsStartDatePickerOpen + }} + popUpContentProps={{}} + /> + + ); + }} + /> + { + return ( + + { + onChange(date); + setIsEndDatePickerOpen(false); + }} + popUpProps={{ + open: isEndDatePickerOpen, + onOpenChange: setIsEndDatePickerOpen + }} + popUpContentProps={{}} + /> + + ); + }} + />
-
- {subscription && ( - 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."} - /> - )} + return ( +
+ + {t("common.head-title", { title: t("billing.title") })} + + + +
- ); + ); } -Activity.requireAuth = true; +export default Logs; +Logs.requireAuth = true; \ No newline at end of file diff --git a/frontend/src/pages/project/[id]/logs/index.tsx b/frontend/src/pages/project/[id]/logs/index.tsx index ba24ba88f..0c44abbef 100644 --- a/frontend/src/pages/project/[id]/logs/index.tsx +++ b/frontend/src/pages/project/[id]/logs/index.tsx @@ -1,23 +1,197 @@ +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import Head from "next/head"; +import { useRouter } from "next/router"; -import { LogsPage } from "@app/views/Project/LogsPage"; +import Button from "@app/components/basic/buttons/Button"; +import EventFilter from "@app/components/basic/EventFilter"; +import { UpgradePlanModal } from "@app/components/v2"; +import { useSubscription } from "@app/context"; +import ActivitySideBar from "@app/ee/components/ActivitySideBar"; +import { usePopUp } from "@app/hooks/usePopUp"; -const Logs = () => { - const { t } = useTranslation(); +import getProjectLogs from "../../../../ee/api/secrets/GetProjectLogs"; +import ActivityTable from "../../../../ee/components/ActivityTable"; - return ( -
- - {t("common.head-title", { title: t("billing.title") })} - - - - -
- ); +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[]; + }; + }[]; } -export default Logs; +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 + */ +export default function Activity() { + const router = useRouter(); + const [eventChosen, setEventChosen] = useState(""); + const [logsData, setLogsData] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [currentOffset, setCurrentOffset] = useState(0); + const currentLimit = 10; + const [currentSidebarAction, toggleSidebar] = useState(); + 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 ( +
+ + Audit Logs + + + + {currentSidebarAction && ( + + )} +
+
+

{t("activity.title")}

+
+

{t("activity.subtitle")}

+
+
+ +
+ +
+
+
+
+ {subscription && ( + 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."} + /> + )} +
+ ); +} + +Activity.requireAuth = true; -Logs.requireAuth = true; \ No newline at end of file diff --git a/frontend/src/views/DashboardPage/components/SecretDetailDrawer/SecretDetailDrawer.tsx b/frontend/src/views/DashboardPage/components/SecretDetailDrawer/SecretDetailDrawer.tsx index ab5c8719f..182eea8f4 100644 --- a/frontend/src/views/DashboardPage/components/SecretDetailDrawer/SecretDetailDrawer.tsx +++ b/frontend/src/views/DashboardPage/components/SecretDetailDrawer/SecretDetailDrawer.tsx @@ -27,7 +27,7 @@ type Props = { onEnvCompare: (secretKey: string) => void; secretVersion?: Array<{ id: string; createdAt: string; value: string }>; // 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; onSave: () => void; }; @@ -45,6 +45,15 @@ export const SecretDetailDrawer = ({ const [canRevealSecOverride, setCanRevealSecOverride] = useToggle(); const { register, setValue, control, getValues } = useFormContext(); + + const secKey = useWatch({ + control, + name: `secrets.${index}.key`, + disabled: false, + exact: true + }); + + console.log("secKeyyy", secKey); const overrideAction = useWatch({ control, name: `secrets.${index}.overrideAction` }); const isOverridden = diff --git a/frontend/src/views/Project/LogsPage/LogsPage.tsx b/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx similarity index 90% rename from frontend/src/views/Project/LogsPage/LogsPage.tsx rename to frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx index c17fd7092..0a192110b 100644 --- a/frontend/src/views/Project/LogsPage/LogsPage.tsx +++ b/frontend/src/views/Project/AuditLogsPage/AuditLogsPage.tsx @@ -2,7 +2,7 @@ import { LogsSection } from "./components"; -export const LogsPage = () => { +export const AuditLogsPage = () => { return (
diff --git a/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx b/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx similarity index 97% rename from frontend/src/views/Project/LogsPage/components/LogsFilter.tsx rename to frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx index 88523e012..9b2be6f4c 100644 --- a/frontend/src/views/Project/LogsPage/components/LogsFilter.tsx +++ b/frontend/src/views/Project/AuditLogsPage/components/LogsFilter.tsx @@ -73,7 +73,8 @@ export const LogsFilter = ({ className="w-40 mr-4" >