add extra org policy checks to middlewares

This commit is contained in:
miloschwartz
2025-12-03 15:50:24 -05:00
parent 9be5a01173
commit 5afff3c662
18 changed files with 285 additions and 34 deletions

View File

@@ -79,6 +79,7 @@ declare global {
userOrgIds?: string[]; userOrgIds?: string[];
remoteExitNode?: RemoteExitNode; remoteExitNode?: RemoteExitNode;
siteResource?: SiteResource; siteResource?: SiteResource;
orgPolicyAllowed?: boolean;
} }
} }
} }

View File

@@ -5,6 +5,7 @@ import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { canUserAccessResource } from "@server/auth/canUserAccessResource"; import { canUserAccessResource } from "@server/auth/canUserAccessResource";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifyAccessTokenAccess( export async function verifyAccessTokenAccess(
req: Request, req: Request,
@@ -96,6 +97,24 @@ export async function verifyAccessTokenAccess(
req.userOrgId = resource[0].orgId!; req.userOrgId = resource[0].orgId!;
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
const resourceAllowed = await canUserAccessResource({ const resourceAllowed = await canUserAccessResource({
userId, userId,
resourceId, resourceId,

View File

@@ -4,6 +4,7 @@ import { roles, userOrgs } from "@server/db";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifyAdmin( export async function verifyAdmin(
req: Request, req: Request,
@@ -43,6 +44,24 @@ export async function verifyAdmin(
); );
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
const userRole = await db const userRole = await db
.select() .select()
.from(roles) .from(roles)

View File

@@ -4,6 +4,7 @@ import { userOrgs, apiKeys, apiKeyOrg } from "@server/db";
import { and, eq, or } from "drizzle-orm"; import { and, eq, or } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifyApiKeyAccess( export async function verifyApiKeyAccess(
req: Request, req: Request,
@@ -84,6 +85,24 @@ export async function verifyApiKeyAccess(
); );
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
const userOrgRoleId = req.userOrg.roleId; const userOrgRoleId = req.userOrg.roleId;
req.userOrgRoleId = userOrgRoleId; req.userOrgRoleId = userOrgRoleId;

View File

@@ -4,6 +4,7 @@ import { userOrgs, clients, roleClients, userClients } from "@server/db";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifyClientAccess( export async function verifyClientAccess(
req: Request, req: Request,
@@ -75,6 +76,24 @@ export async function verifyClientAccess(
); );
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
const userOrgRoleId = req.userOrg.roleId; const userOrgRoleId = req.userOrg.roleId;
req.userOrgRoleId = userOrgRoleId; req.userOrgRoleId = userOrgRoleId;
req.userOrgId = client.orgId; req.userOrgId = client.orgId;

View File

@@ -4,6 +4,7 @@ import { userOrgs, apiKeyOrg } from "@server/db";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifyDomainAccess( export async function verifyDomainAccess(
req: Request, req: Request,
@@ -78,6 +79,24 @@ export async function verifyDomainAccess(
); );
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
const userOrgRoleId = req.userOrg.roleId; const userOrgRoleId = req.userOrg.roleId;
req.userOrgRoleId = userOrgRoleId; req.userOrgRoleId = userOrgRoleId;

View File

@@ -47,20 +47,22 @@ export async function verifyOrgAccess(
); );
} }
const policyCheck = await checkOrgAccessPolicy({ if (req.orgPolicyAllowed === undefined) {
orgId, const policyCheck = await checkOrgAccessPolicy({
userId, orgId,
session: req.session userId,
}); session: req.session
});
if (!policyCheck.allowed || policyCheck.error) { req.orgPolicyAllowed = policyCheck.allowed;
return next( if (!policyCheck.allowed || policyCheck.error) {
createHttpError( return next(
HttpCode.FORBIDDEN, createHttpError(
"Failed organization access policy check: " + HttpCode.FORBIDDEN,
(policyCheck.error || "Unknown error") "Failed organization access policy check: " +
) (policyCheck.error || "Unknown error")
); )
);
}
} }
// User has access, attach the user's role to the request for potential future use // User has access, attach the user's role to the request for potential future use

View File

@@ -1,14 +1,10 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { db } from "@server/db"; import { db } from "@server/db";
import { import { resources, userOrgs, userResources, roleResources } from "@server/db";
resources,
userOrgs,
userResources,
roleResources,
} from "@server/db";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifyResourceAccess( export async function verifyResourceAccess(
req: Request, req: Request,
@@ -73,6 +69,24 @@ export async function verifyResourceAccess(
); );
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
const userOrgRoleId = req.userOrg.roleId; const userOrgRoleId = req.userOrg.roleId;
req.userOrgRoleId = userOrgRoleId; req.userOrgRoleId = userOrgRoleId;
req.userOrgId = resource[0].orgId; req.userOrgId = resource[0].orgId;

View File

@@ -5,6 +5,7 @@ import { and, eq, inArray } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import logger from "@server/logger"; import logger from "@server/logger";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifyRoleAccess( export async function verifyRoleAccess(
req: Request, req: Request,
@@ -105,6 +106,33 @@ export async function verifyRoleAccess(
req.userOrgRoleId = userOrg[0].roleId; req.userOrgRoleId = userOrg[0].roleId;
} }
if (!req.userOrg) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization"
)
);
}
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
return next(); return next();
} catch (error) { } catch (error) {
logger.error("Error verifying role access:", error); logger.error("Error verifying role access:", error);
@@ -116,4 +144,3 @@ export async function verifyRoleAccess(
); );
} }
} }

View File

@@ -4,6 +4,7 @@ import { clients } from "@server/db";
import { and, eq, inArray } from "drizzle-orm"; import { and, eq, inArray } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifySetResourceClients( export async function verifySetResourceClients(
req: Request, req: Request,
@@ -11,9 +12,12 @@ export async function verifySetResourceClients(
next: NextFunction next: NextFunction
) { ) {
const userId = req.user!.userId; const userId = req.user!.userId;
const singleClientId = req.params.clientId || req.body.clientId || req.query.clientId; const singleClientId =
req.params.clientId || req.body.clientId || req.query.clientId;
const { clientIds } = req.body; const { clientIds } = req.body;
const allClientIds = clientIds || (singleClientId ? [parseInt(singleClientId as string)] : []); const allClientIds =
clientIds ||
(singleClientId ? [parseInt(singleClientId as string)] : []);
if (!userId) { if (!userId) {
return next( return next(
@@ -30,6 +34,24 @@ export async function verifySetResourceClients(
); );
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
if (allClientIds.length === 0) { if (allClientIds.length === 0) {
return next(); return next();
} }
@@ -66,4 +88,3 @@ export async function verifySetResourceClients(
); );
} }
} }

View File

@@ -4,6 +4,7 @@ import { userOrgs } from "@server/db";
import { and, eq, inArray, or } from "drizzle-orm"; import { and, eq, inArray, or } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifySetResourceUsers( export async function verifySetResourceUsers(
req: Request, req: Request,
@@ -28,6 +29,24 @@ export async function verifySetResourceUsers(
); );
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
if (!userIds) { if (!userIds) {
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid user IDs")); return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid user IDs"));
} }

View File

@@ -1,16 +1,11 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { db } from "@server/db"; import { db } from "@server/db";
import { import { sites, userOrgs, userSites, roleSites, roles } from "@server/db";
sites,
userOrgs,
userSites,
roleSites,
roles,
} from "@server/db";
import { and, eq, or } from "drizzle-orm"; import { and, eq, or } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import logger from "@server/logger"; import logger from "@server/logger";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifySiteAccess( export async function verifySiteAccess(
req: Request, req: Request,
@@ -82,6 +77,24 @@ export async function verifySiteAccess(
); );
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
const userOrgRoleId = req.userOrg.roleId; const userOrgRoleId = req.userOrg.roleId;
req.userOrgRoleId = userOrgRoleId; req.userOrgRoleId = userOrgRoleId;
req.userOrgId = site[0].orgId; req.userOrgId = site[0].orgId;

View File

@@ -5,6 +5,7 @@ import { eq, and } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import logger from "@server/logger"; import logger from "@server/logger";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifySiteResourceAccess( export async function verifySiteResourceAccess(
req: Request, req: Request,
@@ -90,6 +91,24 @@ export async function verifySiteResourceAccess(
); );
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
const userOrgRoleId = req.userOrg.roleId; const userOrgRoleId = req.userOrg.roleId;
req.userOrgRoleId = userOrgRoleId; req.userOrgRoleId = userOrgRoleId;
req.userOrgId = siteResource.orgId; req.userOrgId = siteResource.orgId;

View File

@@ -5,6 +5,7 @@ import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { canUserAccessResource } from "../auth/canUserAccessResource"; import { canUserAccessResource } from "../auth/canUserAccessResource";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifyTargetAccess( export async function verifyTargetAccess(
req: Request, req: Request,
@@ -102,6 +103,26 @@ export async function verifyTargetAccess(
req.userOrgId = resource[0].orgId!; req.userOrgId = resource[0].orgId!;
} }
const orgId = req.userOrg.orgId;
if (req.orgPolicyAllowed === undefined && orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
const resourceAllowed = await canUserAccessResource({ const resourceAllowed = await canUserAccessResource({
userId, userId,
resourceId, resourceId,

View File

@@ -4,6 +4,7 @@ import { userOrgs } from "@server/db";
import { and, eq, or } from "drizzle-orm"; import { and, eq, or } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
export async function verifyUserAccess( export async function verifyUserAccess(
req: Request, req: Request,
@@ -47,6 +48,24 @@ export async function verifyUserAccess(
); );
} }
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
return next(); return next();
} catch (error) { } catch (error) {
return next( return next(

View File

@@ -5,7 +5,7 @@ import { clients, Olm } from "@server/db";
import { eq, lt, isNull, and, or } from "drizzle-orm"; import { eq, lt, isNull, and, or } from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import { validateSessionToken } from "@server/auth/sessions/app"; import { validateSessionToken } from "@server/auth/sessions/app";
import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy"; import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { sendTerminateClient } from "../client/terminate"; import { sendTerminateClient } from "../client/terminate";
// Track if the offline checker interval is running // Track if the offline checker interval is running

View File

@@ -32,7 +32,7 @@ import {
} from "@server/lib/ip"; } from "@server/lib/ip";
import { generateRemoteSubnets } from "@server/lib/ip"; import { generateRemoteSubnets } from "@server/lib/ip";
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations"; import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy"; import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { validateSessionToken } from "@server/auth/sessions/app"; import { validateSessionToken } from "@server/auth/sessions/app";
import config from "@server/lib/config"; import config from "@server/lib/config";

View File

@@ -32,7 +32,7 @@ import {
} from "@server/lib/ip"; } from "@server/lib/ip";
import { generateRemoteSubnets } from "@server/lib/ip"; import { generateRemoteSubnets } from "@server/lib/ip";
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations"; import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy"; import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { validateSessionToken } from "@server/auth/sessions/app"; import { validateSessionToken } from "@server/auth/sessions/app";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { import {