mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-02 05:03:55 +00:00
Compare commits
5 Commits
dependabot
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
280cbb6e22 | ||
|
|
c20babcb53 | ||
|
|
768eebe2cd | ||
|
|
81c1a1da9c | ||
|
|
52f26396ac |
@@ -1102,6 +1102,12 @@
|
||||
"actionGetUser": "Get User",
|
||||
"actionGetOrgUser": "Get Organization User",
|
||||
"actionListOrgDomains": "List Organization Domains",
|
||||
"actionGetDomain": "Get Domain",
|
||||
"actionCreateOrgDomain": "Create Domain",
|
||||
"actionUpdateOrgDomain": "Update Domain",
|
||||
"actionDeleteOrgDomain": "Delete Domain",
|
||||
"actionGetDNSRecords": "Get DNS Records",
|
||||
"actionRestartOrgDomain": "Restart Domain",
|
||||
"actionCreateSite": "Create Site",
|
||||
"actionDeleteSite": "Delete Site",
|
||||
"actionGetSite": "Get Site",
|
||||
|
||||
@@ -14,3 +14,4 @@ export * from "./verifyApiKeyApiKeyAccess";
|
||||
export * from "./verifyApiKeyClientAccess";
|
||||
export * from "./verifyApiKeySiteResourceAccess";
|
||||
export * from "./verifyApiKeyIdpAccess";
|
||||
export * from "./verifyApiKeyDomainAccess";
|
||||
|
||||
90
server/middlewares/integration/verifyApiKeyDomainAccess.ts
Normal file
90
server/middlewares/integration/verifyApiKeyDomainAccess.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { db, domains, orgDomains, apiKeyOrg } from "@server/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
|
||||
export async function verifyApiKeyDomainAccess(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
try {
|
||||
const apiKey = req.apiKey;
|
||||
const domainId =
|
||||
req.params.domainId || req.body.domainId || req.query.domainId;
|
||||
const orgId = req.params.orgId;
|
||||
|
||||
if (!apiKey) {
|
||||
return next(
|
||||
createHttpError(HttpCode.UNAUTHORIZED, "Key not authenticated")
|
||||
);
|
||||
}
|
||||
|
||||
if (!domainId) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid domain ID")
|
||||
);
|
||||
}
|
||||
|
||||
if (apiKey.isRoot) {
|
||||
// Root keys can access any domain in any org
|
||||
return next();
|
||||
}
|
||||
|
||||
// Verify domain exists and belongs to the organization
|
||||
const [domain] = await db
|
||||
.select()
|
||||
.from(domains)
|
||||
.innerJoin(orgDomains, eq(orgDomains.domainId, domains.domainId))
|
||||
.where(
|
||||
and(
|
||||
eq(orgDomains.domainId, domainId),
|
||||
eq(orgDomains.orgId, orgId)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!domain) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Domain with ID ${domainId} not found in organization ${orgId}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Verify the API key has access to this organization
|
||||
if (!req.apiKeyOrg) {
|
||||
const apiKeyOrgRes = await db
|
||||
.select()
|
||||
.from(apiKeyOrg)
|
||||
.where(
|
||||
and(
|
||||
eq(apiKeyOrg.apiKeyId, apiKey.apiKeyId),
|
||||
eq(apiKeyOrg.orgId, orgId)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
req.apiKeyOrg = apiKeyOrgRes[0];
|
||||
}
|
||||
|
||||
if (!req.apiKeyOrg) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.FORBIDDEN,
|
||||
"Key does not have access to this organization"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return next();
|
||||
} catch (error) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Error verifying domain access"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,8 @@ import {
|
||||
verifyApiKeyClientAccess,
|
||||
verifyApiKeySiteResourceAccess,
|
||||
verifyApiKeySetResourceClients,
|
||||
verifyLimits
|
||||
verifyLimits,
|
||||
verifyApiKeyDomainAccess
|
||||
} from "@server/middlewares";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { Router } from "express";
|
||||
@@ -347,6 +348,56 @@ authenticated.get(
|
||||
domain.listDomains
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/domain/:domainId",
|
||||
verifyApiKeyOrgAccess,
|
||||
verifyApiKeyDomainAccess,
|
||||
verifyApiKeyHasAction(ActionsEnum.getDomain),
|
||||
domain.getDomain
|
||||
);
|
||||
|
||||
authenticated.put(
|
||||
"/org/:orgId/domain",
|
||||
verifyApiKeyOrgAccess,
|
||||
verifyApiKeyHasAction(ActionsEnum.createOrgDomain),
|
||||
logActionAudit(ActionsEnum.createOrgDomain),
|
||||
domain.createOrgDomain
|
||||
);
|
||||
|
||||
authenticated.patch(
|
||||
"/org/:orgId/domain/:domainId",
|
||||
verifyApiKeyOrgAccess,
|
||||
verifyApiKeyDomainAccess,
|
||||
verifyApiKeyHasAction(ActionsEnum.updateOrgDomain),
|
||||
domain.updateOrgDomain
|
||||
);
|
||||
|
||||
authenticated.delete(
|
||||
"/org/:orgId/domain/:domainId",
|
||||
verifyApiKeyOrgAccess,
|
||||
verifyApiKeyDomainAccess,
|
||||
verifyApiKeyHasAction(ActionsEnum.deleteOrgDomain),
|
||||
logActionAudit(ActionsEnum.deleteOrgDomain),
|
||||
domain.deleteAccountDomain
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/domain/:domainId/dns-records",
|
||||
verifyApiKeyOrgAccess,
|
||||
verifyApiKeyDomainAccess,
|
||||
verifyApiKeyHasAction(ActionsEnum.getDNSRecords),
|
||||
domain.getDNSRecords
|
||||
);
|
||||
|
||||
authenticated.post(
|
||||
"/org/:orgId/domain/:domainId/restart",
|
||||
verifyApiKeyOrgAccess,
|
||||
verifyApiKeyDomainAccess,
|
||||
verifyApiKeyHasAction(ActionsEnum.restartOrgDomain),
|
||||
logActionAudit(ActionsEnum.restartOrgDomain),
|
||||
domain.restartOrgDomain
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/invitations",
|
||||
verifyApiKeyOrgAccess,
|
||||
|
||||
@@ -69,15 +69,16 @@ export function LayoutMobileMenu({
|
||||
<SheetDescription className="sr-only">
|
||||
{t("navbarDescription")}
|
||||
</SheetDescription>
|
||||
<div className="flex-1 overflow-y-auto relative">
|
||||
<div className="px-1">
|
||||
<div className="w-full border-b border-border">
|
||||
<div className="px-1 shrink-0">
|
||||
<OrgSelector
|
||||
orgId={orgId}
|
||||
orgs={orgs}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full border-b border-border" />
|
||||
<div className="px-3 pt-3">
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto relative">
|
||||
<div className="px-3">
|
||||
{!isAdminPage &&
|
||||
user.serverAdmin && (
|
||||
<div className="mb-1">
|
||||
|
||||
@@ -31,7 +31,6 @@ function getActionsCategories(root: boolean) {
|
||||
[t("actionListInvitations")]: "listInvitations",
|
||||
[t("actionRemoveUser")]: "removeUser",
|
||||
[t("actionListUsers")]: "listUsers",
|
||||
[t("actionListOrgDomains")]: "listOrgDomains",
|
||||
[t("updateOrgUser")]: "updateOrgUser",
|
||||
[t("createOrgUser")]: "createOrgUser",
|
||||
[t("actionApplyBlueprint")]: "applyBlueprint",
|
||||
@@ -39,6 +38,16 @@ function getActionsCategories(root: boolean) {
|
||||
[t("actionGetBlueprint")]: "getBlueprint"
|
||||
},
|
||||
|
||||
Domain: {
|
||||
[t("actionListOrgDomains")]: "listOrgDomains",
|
||||
[t("actionGetDomain")]: "getDomain",
|
||||
[t("actionCreateOrgDomain")]: "createOrgDomain",
|
||||
[t("actionUpdateOrgDomain")]: "updateOrgDomain",
|
||||
[t("actionDeleteOrgDomain")]: "deleteOrgDomain",
|
||||
[t("actionGetDNSRecords")]: "getDNSRecords",
|
||||
[t("actionRestartOrgDomain")]: "restartOrgDomain"
|
||||
},
|
||||
|
||||
Site: {
|
||||
[t("actionCreateSite")]: "createSite",
|
||||
[t("actionDeleteSite")]: "deleteSite",
|
||||
|
||||
Reference in New Issue
Block a user