mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-01 08:16:44 +00:00
Working on licencing
This commit is contained in:
@@ -1921,5 +1921,9 @@
|
|||||||
"requestLogs": "Request Logs",
|
"requestLogs": "Request Logs",
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
"location": "Location",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,9 @@ export enum ActionsEnum {
|
|||||||
updateLoginPage = "updateLoginPage",
|
updateLoginPage = "updateLoginPage",
|
||||||
getLoginPage = "getLoginPage",
|
getLoginPage = "getLoginPage",
|
||||||
deleteLoginPage = "deleteLoginPage",
|
deleteLoginPage = "deleteLoginPage",
|
||||||
applyBlueprint = "applyBlueprint"
|
applyBlueprint = "applyBlueprint",
|
||||||
|
viewLogs = "viewLogs",
|
||||||
|
exportLogs = "exportLogs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkUserActionPermission(
|
export async function checkUserActionPermission(
|
||||||
|
|||||||
@@ -16,3 +16,4 @@ export * from "./verifyRemoteExitNodeAccess";
|
|||||||
export * from "./verifyIdpAccess";
|
export * from "./verifyIdpAccess";
|
||||||
export * from "./verifyLoginPageAccess";
|
export * from "./verifyLoginPageAccess";
|
||||||
export * from "./logActionAudit";
|
export * from "./logActionAudit";
|
||||||
|
export * from "./verifySubscription";
|
||||||
50
server/private/middlewares/verifySubscription.ts
Normal file
50
server/private/middlewares/verifySubscription.ts
Normal 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"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,8 @@ import {
|
|||||||
verifyCertificateAccess,
|
verifyCertificateAccess,
|
||||||
verifyIdpAccess,
|
verifyIdpAccess,
|
||||||
verifyLoginPageAccess,
|
verifyLoginPageAccess,
|
||||||
verifyRemoteExitNodeAccess
|
verifyRemoteExitNodeAccess,
|
||||||
|
verifyValidSubscription
|
||||||
} from "#private/middlewares";
|
} from "#private/middlewares";
|
||||||
import rateLimit, { ipKeyGenerator } from "express-rate-limit";
|
import rateLimit, { ipKeyGenerator } from "express-rate-limit";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
@@ -348,20 +349,38 @@ authenticated.post(
|
|||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/logs/action",
|
"/org/:orgId/logs/action",
|
||||||
|
verifyValidLicense,
|
||||||
|
verifyValidSubscription,
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.exportLogs),
|
||||||
logs.queryActionAuditLogs
|
logs.queryActionAuditLogs
|
||||||
)
|
)
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/logs/action/export",
|
"/org/:orgId/logs/action/export",
|
||||||
|
verifyValidLicense,
|
||||||
|
verifyValidSubscription,
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.exportLogs),
|
||||||
|
logActionAudit(ActionsEnum.exportLogs),
|
||||||
logs.exportActionAuditLogs
|
logs.exportActionAuditLogs
|
||||||
)
|
)
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/logs/access",
|
"/org/:orgId/logs/access",
|
||||||
|
verifyValidLicense,
|
||||||
|
verifyValidSubscription,
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.exportLogs),
|
||||||
logs.queryAccessAuditLogs
|
logs.queryAccessAuditLogs
|
||||||
)
|
)
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/logs/access/export",
|
"/org/:orgId/logs/access/export",
|
||||||
|
verifyValidLicense,
|
||||||
|
verifyValidSubscription,
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.exportLogs),
|
||||||
|
logActionAudit(ActionsEnum.exportLogs),
|
||||||
logs.exportAccessAuditLogs
|
logs.exportAccessAuditLogs
|
||||||
)
|
)
|
||||||
@@ -1,15 +1,10 @@
|
|||||||
import { db, requestAuditLog } from "@server/db";
|
|
||||||
import { registry } from "@server/openApi";
|
import { registry } from "@server/openApi";
|
||||||
import { NextFunction } from "express";
|
import { NextFunction } from "express";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { eq, gt, lt, and, count } from "drizzle-orm";
|
|
||||||
import { OpenAPITags } from "@server/openApi";
|
import { OpenAPITags } from "@server/openApi";
|
||||||
import { z } from "zod";
|
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { fromError } from "zod-validation-error";
|
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 logger from "@server/logger";
|
||||||
import { queryAccessAuditLogsQuery, queryRequestAuditLogsParams, queryRequest } from "./queryRequstAuditLog";
|
import { queryAccessAuditLogsQuery, queryRequestAuditLogsParams, queryRequest } from "./queryRequstAuditLog";
|
||||||
import { generateCSV } from "./generateCSV";
|
import { generateCSV } from "./generateCSV";
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import createHttpError from "http-errors";
|
|||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { createStore } from "#dynamic/lib/rateLimitStore";
|
import { createStore } from "#dynamic/lib/rateLimitStore";
|
||||||
import { logActionAudit } from "#dynamic/middlewares";
|
import { logActionAudit } from "#dynamic/middlewares";
|
||||||
|
import { log } from "console";
|
||||||
|
|
||||||
// Root routes
|
// Root routes
|
||||||
export const unauthenticated = Router();
|
export const unauthenticated = Router();
|
||||||
@@ -860,6 +861,21 @@ authenticated.delete(
|
|||||||
domain.deleteAccountDomain,
|
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
|
// Auth routes
|
||||||
export const authRouter = Router();
|
export const authRouter = Router();
|
||||||
unauthenticated.use("/auth", authRouter);
|
unauthenticated.use("/auth", authRouter);
|
||||||
@@ -1181,13 +1197,3 @@ authRouter.delete(
|
|||||||
}),
|
}),
|
||||||
auth.deleteSecurityKey
|
auth.deleteSecurityKey
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
|
||||||
"/org/:orgId/logs/request",
|
|
||||||
logs.queryRequestAuditLogs
|
|
||||||
)
|
|
||||||
|
|
||||||
authenticated.get(
|
|
||||||
"/org/:orgId/logs/request/export",
|
|
||||||
logs.exportRequestAuditLogs
|
|
||||||
)
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
|
||||||
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
|
||||||
import { verifySession } from "@app/lib/auth/verifySession";
|
import { verifySession } from "@app/lib/auth/verifySession";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { cache } from "react";
|
import { cache } from "react";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
|
|
||||||
type GeneralSettingsProps = {
|
type GeneralSettingsProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -14,8 +11,6 @@ export default async function GeneralSettingsPage({
|
|||||||
children,
|
children,
|
||||||
params
|
params
|
||||||
}: GeneralSettingsProps) {
|
}: GeneralSettingsProps) {
|
||||||
const { orgId } = await params;
|
|
||||||
|
|
||||||
const getUser = cache(verifySession);
|
const getUser = cache(verifySession);
|
||||||
const user = await getUser();
|
const user = await getUser();
|
||||||
|
|
||||||
@@ -23,31 +18,5 @@ export default async function GeneralSettingsPage({
|
|||||||
redirect(`/`);
|
redirect(`/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = await getTranslations();
|
return children;
|
||||||
|
|
||||||
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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { DateTimeValue } from "@app/components/DateTimePicker";
|
|||||||
import { Key, RouteOff, User, Lock, Unlock, ArrowUpRight } from "lucide-react";
|
import { Key, RouteOff, User, Lock, Unlock, ArrowUpRight } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ColumnFilter } from "@app/components/ColumnFilter";
|
import { ColumnFilter } from "@app/components/ColumnFilter";
|
||||||
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||||
|
|
||||||
export default function GeneralPage() {
|
export default function GeneralPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -755,6 +756,11 @@ export default function GeneralPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<SettingsSectionTitle
|
||||||
|
title={t('requestLogs')}
|
||||||
|
description={t('requestLogsDescription')}
|
||||||
|
/>
|
||||||
|
|
||||||
<LogDataTable
|
<LogDataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={rows}
|
data={rows}
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ import {
|
|||||||
Server,
|
Server,
|
||||||
Zap,
|
Zap,
|
||||||
CreditCard,
|
CreditCard,
|
||||||
Logs
|
Logs,
|
||||||
|
SquareMousePointer,
|
||||||
|
ScanEye
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
export type SidebarNavSection = {
|
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",
|
heading: "Organization",
|
||||||
items: [
|
items: [
|
||||||
@@ -139,11 +161,6 @@ export const orgNavSections = (
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{
|
|
||||||
title: "sidebarLogs",
|
|
||||||
href: "/{orgId}/settings/logs/request",
|
|
||||||
icon: <Logs className="h-4 w-4" />
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "sidebarSettings",
|
title: "sidebarSettings",
|
||||||
href: "/{orgId}/settings/general",
|
href: "/{orgId}/settings/general",
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import React from "react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useParams, usePathname } from "next/navigation";
|
import { useParams, usePathname } from "next/navigation";
|
||||||
import { cn } from "@app/lib/cn";
|
import { cn } from "@app/lib/cn";
|
||||||
import { buttonVariants } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@app/components/ui/badge";
|
import { Badge } from "@app/components/ui/badge";
|
||||||
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
|
||||||
|
|
||||||
export type HorizontalTabs = Array<{
|
export type HorizontalTabs = Array<{
|
||||||
title: string;
|
title: string;
|
||||||
@@ -30,6 +30,7 @@ export function HorizontalTabs({
|
|||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
|
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
|
||||||
|
const subscription = useSubscriptionStatusContext();
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
function hydrateHref(href: string) {
|
function hydrateHref(href: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user