Working on licencing

This commit is contained in:
Owen
2025-10-24 10:11:28 -07:00
parent adefbdbeb3
commit 68f0c4df3a
11 changed files with 129 additions and 59 deletions

View File

@@ -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"
}

View File

@@ -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(

View File

@@ -15,4 +15,5 @@ export * from "./verifyCertificateAccess";
export * from "./verifyRemoteExitNodeAccess";
export * from "./verifyIdpAccess";
export * from "./verifyLoginPageAccess";
export * from "./logActionAudit";
export * from "./logActionAudit";
export * from "./verifySubscription";

View File

@@ -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"
)
);
}
}

View File

@@ -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
)

View File

@@ -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";

View File

@@ -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
)
);

View File

@@ -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 (
<>
<SettingsSectionTitle
title={t("logs")}
description={t("logsSettingsDescription")}
/>
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>
</>
);
return children;
}

View File

@@ -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 (
<>
<SettingsSectionTitle
title={t('requestLogs')}
description={t('requestLogsDescription')}
/>
<LogDataTable
columns={columns}
data={rows}

View File

@@ -17,7 +17,9 @@ import {
Server,
Zap,
CreditCard,
Logs
Logs,
SquareMousePointer,
ScanEye
} from "lucide-react";
export type SidebarNavSection = {
@@ -113,6 +115,26 @@ export const orgNavSections = (
}
]
},
{
heading: "Analytics",
items: [
{
title: "sidebarLogsRequest",
href: "/{orgId}/settings/logs/request",
icon: <SquareMousePointer className="h-4 w-4" />
},
{
title: "sidebarLogsAccess",
href: "/{orgId}/settings/logs/access",
icon: <ScanEye className="h-4 w-4" />
},
{
title: "sidebarLogsAction",
href: "/{orgId}/settings/logs/action",
icon: <Logs className="h-4 w-4" />
},
]
},
{
heading: "Organization",
items: [
@@ -139,11 +161,6 @@ export const orgNavSections = (
}
]
: []),
{
title: "sidebarLogs",
href: "/{orgId}/settings/logs/request",
icon: <Logs className="h-4 w-4" />
},
{
title: "sidebarSettings",
href: "/{orgId}/settings/general",

View File

@@ -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) {