diff --git a/messages/en-US.json b/messages/en-US.json
index 9157e84c..0a62ad7e 100644
--- a/messages/en-US.json
+++ b/messages/en-US.json
@@ -1921,5 +1921,9 @@
"requestLogs": "Request Logs",
"host": "Host",
"location": "Location",
- "actionLogs": "Action Logs"
+ "actionLogs": "Action Logs",
+ "sidebarLogsRequest": "Request Logs",
+ "sidebarLogsAccess": "Access Logs",
+ "sidebarLogsAction": "Action Logs",
+ "requestLogsDescription": "View detailed pre-request logs for resources in this organization"
}
diff --git a/server/auth/actions.ts b/server/auth/actions.ts
index e48bc502..1d22cff1 100644
--- a/server/auth/actions.ts
+++ b/server/auth/actions.ts
@@ -116,7 +116,9 @@ export enum ActionsEnum {
updateLoginPage = "updateLoginPage",
getLoginPage = "getLoginPage",
deleteLoginPage = "deleteLoginPage",
- applyBlueprint = "applyBlueprint"
+ applyBlueprint = "applyBlueprint",
+ viewLogs = "viewLogs",
+ exportLogs = "exportLogs"
}
export async function checkUserActionPermission(
diff --git a/server/private/middlewares/index.ts b/server/private/middlewares/index.ts
index 32aa1a1f..bb4d9c05 100644
--- a/server/private/middlewares/index.ts
+++ b/server/private/middlewares/index.ts
@@ -15,4 +15,5 @@ export * from "./verifyCertificateAccess";
export * from "./verifyRemoteExitNodeAccess";
export * from "./verifyIdpAccess";
export * from "./verifyLoginPageAccess";
-export * from "./logActionAudit";
\ No newline at end of file
+export * from "./logActionAudit";
+export * from "./verifySubscription";
\ No newline at end of file
diff --git a/server/private/middlewares/verifySubscription.ts b/server/private/middlewares/verifySubscription.ts
new file mode 100644
index 00000000..5249c026
--- /dev/null
+++ b/server/private/middlewares/verifySubscription.ts
@@ -0,0 +1,50 @@
+/*
+ * This file is part of a proprietary work.
+ *
+ * Copyright (c) 2025 Fossorial, Inc.
+ * All rights reserved.
+ *
+ * This file is licensed under the Fossorial Commercial License.
+ * You may not use this file except in compliance with the License.
+ * Unauthorized use, copying, modification, or distribution is strictly prohibited.
+ *
+ * This file is not licensed under the AGPLv3.
+ */
+
+import { Request, Response, NextFunction } from "express";
+import createHttpError from "http-errors";
+import HttpCode from "@server/types/HttpCode";
+import { build } from "@server/build";
+import { getOrgTierData } from "#private/lib/billing";
+
+export async function verifyValidSubscription(
+ req: Request,
+ res: Response,
+ next: NextFunction
+) {
+ try {
+ if (build != "saas") {
+ return next();
+ }
+
+ const tier = await getOrgTierData(req.params.orgId);
+
+ if (!tier.active) {
+ return next(
+ createHttpError(
+ HttpCode.FORBIDDEN,
+ "Organization does not have an active subscription"
+ )
+ );
+ }
+
+ return next();
+ } catch (e) {
+ return next(
+ createHttpError(
+ HttpCode.INTERNAL_SERVER_ERROR,
+ "Error verifying subscription"
+ )
+ );
+ }
+}
diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts
index 445af82f..ddfabb30 100644
--- a/server/private/routers/external.ts
+++ b/server/private/routers/external.ts
@@ -34,7 +34,8 @@ import {
verifyCertificateAccess,
verifyIdpAccess,
verifyLoginPageAccess,
- verifyRemoteExitNodeAccess
+ verifyRemoteExitNodeAccess,
+ verifyValidSubscription
} from "#private/middlewares";
import rateLimit, { ipKeyGenerator } from "express-rate-limit";
import createHttpError from "http-errors";
@@ -348,20 +349,38 @@ authenticated.post(
authenticated.get(
"/org/:orgId/logs/action",
+ verifyValidLicense,
+ verifyValidSubscription,
+ verifyOrgAccess,
+ verifyUserHasAction(ActionsEnum.exportLogs),
logs.queryActionAuditLogs
)
authenticated.get(
"/org/:orgId/logs/action/export",
+ verifyValidLicense,
+ verifyValidSubscription,
+ verifyOrgAccess,
+ verifyUserHasAction(ActionsEnum.exportLogs),
+ logActionAudit(ActionsEnum.exportLogs),
logs.exportActionAuditLogs
)
authenticated.get(
"/org/:orgId/logs/access",
+ verifyValidLicense,
+ verifyValidSubscription,
+ verifyOrgAccess,
+ verifyUserHasAction(ActionsEnum.exportLogs),
logs.queryAccessAuditLogs
)
authenticated.get(
"/org/:orgId/logs/access/export",
+ verifyValidLicense,
+ verifyValidSubscription,
+ verifyOrgAccess,
+ verifyUserHasAction(ActionsEnum.exportLogs),
+ logActionAudit(ActionsEnum.exportLogs),
logs.exportAccessAuditLogs
)
\ No newline at end of file
diff --git a/server/routers/auditLogs/exportRequstAuditLog.ts b/server/routers/auditLogs/exportRequstAuditLog.ts
index c1fb2872..89df2d3f 100644
--- a/server/routers/auditLogs/exportRequstAuditLog.ts
+++ b/server/routers/auditLogs/exportRequstAuditLog.ts
@@ -1,15 +1,10 @@
-import { db, requestAuditLog } from "@server/db";
import { registry } from "@server/openApi";
import { NextFunction } from "express";
import { Request, Response } from "express";
-import { eq, gt, lt, and, count } from "drizzle-orm";
import { OpenAPITags } from "@server/openApi";
-import { z } from "zod";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
-import { QueryRequestAuditLogResponse } from "@server/routers/auditLogs/types";
-import response from "@server/lib/response";
import logger from "@server/logger";
import { queryAccessAuditLogsQuery, queryRequestAuditLogsParams, queryRequest } from "./queryRequstAuditLog";
import { generateCSV } from "./generateCSV";
diff --git a/server/routers/external.ts b/server/routers/external.ts
index 2b8c270f..335bc252 100644
--- a/server/routers/external.ts
+++ b/server/routers/external.ts
@@ -46,6 +46,7 @@ import createHttpError from "http-errors";
import { build } from "@server/build";
import { createStore } from "#dynamic/lib/rateLimitStore";
import { logActionAudit } from "#dynamic/middlewares";
+import { log } from "console";
// Root routes
export const unauthenticated = Router();
@@ -860,6 +861,21 @@ authenticated.delete(
domain.deleteAccountDomain,
);
+authenticated.get(
+ "/org/:orgId/logs/request",
+ verifyOrgAccess,
+ verifyUserHasAction(ActionsEnum.viewLogs),
+ logs.queryRequestAuditLogs
+)
+
+authenticated.get(
+ "/org/:orgId/logs/request/export",
+ verifyOrgAccess,
+ verifyUserHasAction(ActionsEnum.exportLogs),
+ logActionAudit(ActionsEnum.exportLogs),
+ logs.exportRequestAuditLogs
+)
+
// Auth routes
export const authRouter = Router();
unauthenticated.use("/auth", authRouter);
@@ -1180,14 +1196,4 @@ authRouter.delete(
store: createStore()
}),
auth.deleteSecurityKey
-);
-
-authenticated.get(
- "/org/:orgId/logs/request",
- logs.queryRequestAuditLogs
-)
-
-authenticated.get(
- "/org/:orgId/logs/request/export",
- logs.exportRequestAuditLogs
-)
\ No newline at end of file
+);
\ No newline at end of file
diff --git a/src/app/[orgId]/settings/logs/layout.tsx b/src/app/[orgId]/settings/logs/layout.tsx
index fb1d2071..96958403 100644
--- a/src/app/[orgId]/settings/logs/layout.tsx
+++ b/src/app/[orgId]/settings/logs/layout.tsx
@@ -1,9 +1,6 @@
-import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
-import { HorizontalTabs } from "@app/components/HorizontalTabs";
import { verifySession } from "@app/lib/auth/verifySession";
import { redirect } from "next/navigation";
import { cache } from "react";
-import { getTranslations } from "next-intl/server";
type GeneralSettingsProps = {
children: React.ReactNode;
@@ -14,8 +11,6 @@ export default async function GeneralSettingsPage({
children,
params
}: GeneralSettingsProps) {
- const { orgId } = await params;
-
const getUser = cache(verifySession);
const user = await getUser();
@@ -23,31 +18,5 @@ export default async function GeneralSettingsPage({
redirect(`/`);
}
- const t = await getTranslations();
-
- const navItems = [
- {
- title: t("request"),
- href: `/{orgId}/settings/logs/request`
- },
- {
- title: t("access"),
- href: `/{orgId}/settings/logs/access`
- },
- {
- title: t("action"),
- href: `/{orgId}/settings/logs/action`
- }
- ];
-
- return (
- <>
-
-
- {children}
- >
- );
+ return children;
}
diff --git a/src/app/[orgId]/settings/logs/request/page.tsx b/src/app/[orgId]/settings/logs/request/page.tsx
index dcca97d2..6a1a1659 100644
--- a/src/app/[orgId]/settings/logs/request/page.tsx
+++ b/src/app/[orgId]/settings/logs/request/page.tsx
@@ -12,6 +12,7 @@ import { DateTimeValue } from "@app/components/DateTimePicker";
import { Key, RouteOff, User, Lock, Unlock, ArrowUpRight } from "lucide-react";
import Link from "next/link";
import { ColumnFilter } from "@app/components/ColumnFilter";
+import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
export default function GeneralPage() {
const router = useRouter();
@@ -755,6 +756,11 @@ export default function GeneralPage() {
return (
<>
+
+
+ },
+ {
+ title: "sidebarLogsAccess",
+ href: "/{orgId}/settings/logs/access",
+ icon:
+ },
+ {
+ title: "sidebarLogsAction",
+ href: "/{orgId}/settings/logs/action",
+ icon:
+ },
+ ]
+ },
{
heading: "Organization",
items: [
@@ -139,11 +161,6 @@ export const orgNavSections = (
}
]
: []),
- {
- title: "sidebarLogs",
- href: "/{orgId}/settings/logs/request",
- icon:
- },
{
title: "sidebarSettings",
href: "/{orgId}/settings/general",
diff --git a/src/components/HorizontalTabs.tsx b/src/components/HorizontalTabs.tsx
index b529dbba..5b0fdbab 100644
--- a/src/components/HorizontalTabs.tsx
+++ b/src/components/HorizontalTabs.tsx
@@ -4,10 +4,10 @@ import React from "react";
import Link from "next/link";
import { useParams, usePathname } from "next/navigation";
import { cn } from "@app/lib/cn";
-import { buttonVariants } from "@/components/ui/button";
import { Badge } from "@app/components/ui/badge";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useTranslations } from "next-intl";
+import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
export type HorizontalTabs = Array<{
title: string;
@@ -30,6 +30,7 @@ export function HorizontalTabs({
const pathname = usePathname();
const params = useParams();
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
+ const subscription = useSubscriptionStatusContext();
const t = useTranslations();
function hydrateHref(href: string) {