mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-06 02:36:38 +00:00
Merge branch 'dev' of https://github.com/fosrl/pangolin into dev
This commit is contained in:
@@ -225,8 +225,8 @@ export const siteResources = pgTable("siteResources", {
|
|||||||
enabled: boolean("enabled").notNull().default(true),
|
enabled: boolean("enabled").notNull().default(true),
|
||||||
alias: varchar("alias"),
|
alias: varchar("alias"),
|
||||||
aliasAddress: varchar("aliasAddress"),
|
aliasAddress: varchar("aliasAddress"),
|
||||||
tcpPortRangeString: varchar("tcpPortRangeString"),
|
tcpPortRangeString: varchar("tcpPortRangeString").notNull().default("*"),
|
||||||
udpPortRangeString: varchar("udpPortRangeString"),
|
udpPortRangeString: varchar("udpPortRangeString").notNull().default("*"),
|
||||||
disableIcmp: boolean("disableIcmp").notNull().default(false)
|
disableIcmp: boolean("disableIcmp").notNull().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -253,9 +253,9 @@ export const siteResources = sqliteTable("siteResources", {
|
|||||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||||
alias: text("alias"),
|
alias: text("alias"),
|
||||||
aliasAddress: text("aliasAddress"),
|
aliasAddress: text("aliasAddress"),
|
||||||
tcpPortRangeString: text("tcpPortRangeString"),
|
tcpPortRangeString: text("tcpPortRangeString").notNull().default("*"),
|
||||||
udpPortRangeString: text("udpPortRangeString"),
|
udpPortRangeString: text("udpPortRangeString").notNull().default("*"),
|
||||||
disableIcmp: integer("disableIcmp", { mode: "boolean" })
|
disableIcmp: integer("disableIcmp", { mode: "boolean" }).notNull().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const clientSiteResources = sqliteTable("clientSiteResources", {
|
export const clientSiteResources = sqliteTable("clientSiteResources", {
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ import {
|
|||||||
} from "@server/lib/checkOrgAccessPolicy";
|
} from "@server/lib/checkOrgAccessPolicy";
|
||||||
import { UserType } from "@server/types/UserTypes";
|
import { UserType } from "@server/types/UserTypes";
|
||||||
|
|
||||||
export async function enforceResourceSessionLength(
|
export function enforceResourceSessionLength(
|
||||||
resourceSession: ResourceSession,
|
resourceSession: ResourceSession,
|
||||||
org: Org
|
org: Org
|
||||||
): Promise<{ valid: boolean; error?: string }> {
|
): { valid: boolean; error?: string } {
|
||||||
if (org.maxSessionLengthHours) {
|
if (org.maxSessionLengthHours) {
|
||||||
const sessionIssuedAt = resourceSession.issuedAt; // may be null
|
const sessionIssuedAt = resourceSession.issuedAt; // may be null
|
||||||
const maxSessionLengthHours = org.maxSessionLengthHours;
|
const maxSessionLengthHours = org.maxSessionLengthHours;
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ export async function getTraefikConfig(
|
|||||||
siteTypes: string[],
|
siteTypes: string[],
|
||||||
filterOutNamespaceDomains = false,
|
filterOutNamespaceDomains = false,
|
||||||
generateLoginPageRouters = false,
|
generateLoginPageRouters = false,
|
||||||
allowRawResources = true
|
allowRawResources = true,
|
||||||
|
allowMaintenancePage = true
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
// Get resources with their targets and sites in a single optimized query
|
// Get resources with their targets and sites in a single optimized query
|
||||||
// Start from sites on this exit node, then join to targets and resources
|
// Start from sites on this exit node, then join to targets and resources
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ import {
|
|||||||
resourceHeaderAuthExtendedCompatibility,
|
resourceHeaderAuthExtendedCompatibility,
|
||||||
ResourceHeaderAuthExtendedCompatibility,
|
ResourceHeaderAuthExtendedCompatibility,
|
||||||
orgs,
|
orgs,
|
||||||
requestAuditLog
|
requestAuditLog,
|
||||||
|
Org
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import {
|
import {
|
||||||
resources,
|
resources,
|
||||||
@@ -79,6 +80,7 @@ import { maxmindLookup } from "@server/db/maxmind";
|
|||||||
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import { maxmindAsnLookup } from "@server/db/maxmindAsn";
|
import { maxmindAsnLookup } from "@server/db/maxmindAsn";
|
||||||
|
import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy";
|
||||||
|
|
||||||
// Zod schemas for request validation
|
// Zod schemas for request validation
|
||||||
const getResourceByDomainParamsSchema = z.strictObject({
|
const getResourceByDomainParamsSchema = z.strictObject({
|
||||||
@@ -94,6 +96,12 @@ const getUserOrgRoleParamsSchema = z.strictObject({
|
|||||||
orgId: z.string().min(1, "Organization ID is required")
|
orgId: z.string().min(1, "Organization ID is required")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getUserOrgSessionVerifySchema = z.strictObject({
|
||||||
|
userId: z.string().min(1, "User ID is required"),
|
||||||
|
orgId: z.string().min(1, "Organization ID is required"),
|
||||||
|
sessionId: z.string().min(1, "Session ID is required")
|
||||||
|
});
|
||||||
|
|
||||||
const getRoleResourceAccessParamsSchema = z.strictObject({
|
const getRoleResourceAccessParamsSchema = z.strictObject({
|
||||||
roleId: z
|
roleId: z
|
||||||
.string()
|
.string()
|
||||||
@@ -178,6 +186,7 @@ export type ResourceWithAuth = {
|
|||||||
password: ResourcePassword | null;
|
password: ResourcePassword | null;
|
||||||
headerAuth: ResourceHeaderAuth | null;
|
headerAuth: ResourceHeaderAuth | null;
|
||||||
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
|
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
|
||||||
|
org: Org
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserSessionWithUser = {
|
export type UserSessionWithUser = {
|
||||||
@@ -508,6 +517,7 @@ hybridRouter.get(
|
|||||||
resources.resourceId
|
resources.resourceId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.innerJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||||
.where(eq(resources.fullDomain, domain))
|
.where(eq(resources.fullDomain, domain))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
@@ -542,7 +552,8 @@ hybridRouter.get(
|
|||||||
password: result.resourcePassword,
|
password: result.resourcePassword,
|
||||||
headerAuth: result.resourceHeaderAuth,
|
headerAuth: result.resourceHeaderAuth,
|
||||||
headerAuthExtendedCompatibility:
|
headerAuthExtendedCompatibility:
|
||||||
result.resourceHeaderAuthExtendedCompatibility
|
result.resourceHeaderAuthExtendedCompatibility,
|
||||||
|
org: result.orgs
|
||||||
};
|
};
|
||||||
|
|
||||||
return response<ResourceWithAuth>(res, {
|
return response<ResourceWithAuth>(res, {
|
||||||
@@ -822,6 +833,69 @@ hybridRouter.get(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get user organization role
|
||||||
|
hybridRouter.get(
|
||||||
|
"/user/:userId/org/:orgId/session/:sessionId/verify",
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const parsedParams = getUserOrgSessionVerifySchema.safeParse(
|
||||||
|
req.params
|
||||||
|
);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userId, orgId, sessionId } = parsedParams.data;
|
||||||
|
const remoteExitNode = req.remoteExitNode;
|
||||||
|
|
||||||
|
if (!remoteExitNode || !remoteExitNode.exitNodeId) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Remote exit node not found"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await checkExitNodeOrg(remoteExitNode.exitNodeId, orgId)) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.UNAUTHORIZED,
|
||||||
|
"User is not authorized to access this organization"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessPolicy = await checkOrgAccessPolicy({
|
||||||
|
orgId,
|
||||||
|
userId,
|
||||||
|
sessionId
|
||||||
|
});
|
||||||
|
|
||||||
|
return response(res, {
|
||||||
|
data: accessPolicy,
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "User org access policy retrieved successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Failed to get user org role"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Check if role has access to resource
|
// Check if role has access to resource
|
||||||
hybridRouter.get(
|
hybridRouter.get(
|
||||||
"/role/:roleId/resource/:resourceId/access",
|
"/role/:roleId/resource/:resourceId/access",
|
||||||
|
|||||||
@@ -911,7 +911,7 @@ WantedBy=default.target`
|
|||||||
? "squareOutlinePrimary"
|
? "squareOutlinePrimary"
|
||||||
: "squareOutline"
|
: "squareOutline"
|
||||||
}
|
}
|
||||||
className={`flex-1 min-w-[120px] ${platform === os ? "bg-primary/10" : ""} shadow-none`}
|
className={`flex-1 min-w-30 ${platform === os ? "bg-primary/10" : ""} shadow-none`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPlatform(os);
|
setPlatform(os);
|
||||||
}}
|
}}
|
||||||
@@ -942,7 +942,7 @@ WantedBy=default.target`
|
|||||||
? "squareOutlinePrimary"
|
? "squareOutlinePrimary"
|
||||||
: "squareOutline"
|
: "squareOutline"
|
||||||
}
|
}
|
||||||
className={`flex-1 min-w-[120px] ${architecture === arch ? "bg-primary/10" : ""} shadow-none`}
|
className={`flex-1 min-w-30 ${architecture === arch ? "bg-primary/10" : ""} shadow-none`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setArchitecture(
|
setArchitecture(
|
||||||
arch
|
arch
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ function ProductUpdatesListPopup({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative z-1 cursor-pointer block group",
|
"relative z-1 cursor-pointer block group",
|
||||||
"rounded-md border border-primary/30 bg-gradient-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
|
"rounded-md border border-primary/30 bg-linear-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
|
||||||
"transition duration-300 ease-in-out",
|
"transition duration-300 ease-in-out",
|
||||||
"data-closed:opacity-0 data-closed:translate-y-full"
|
"data-closed:opacity-0 data-closed:translate-y-full"
|
||||||
)}
|
)}
|
||||||
@@ -346,7 +346,7 @@ function NewVersionAvailable({
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative z-2 group cursor-pointer block",
|
"relative z-2 group cursor-pointer block",
|
||||||
"rounded-md border border-primary/30 bg-gradient-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
|
"rounded-md border border-primary/30 bg-linear-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
|
||||||
"transition duration-300 ease-in-out",
|
"transition duration-300 ease-in-out",
|
||||||
"data-closed:opacity-0 data-closed:translate-y-full"
|
"data-closed:opacity-0 data-closed:translate-y-full"
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user