add redirect routes to integration api

This commit is contained in:
miloschwartz
2026-10-02 17:11:06 -04:00
parent 4d1a14c965
commit 22b7411794
6 changed files with 183 additions and 0 deletions
+5
View File
@@ -1512,6 +1512,11 @@
"actionGetVirtualApiKey": "Get Virtual API Key",
"actionListVirtualApiKeys": "List Virtual API Keys",
"actionUpdateVirtualApiKey": "Update Virtual API Key",
"actionCreateRedirect": "Create Redirect",
"actionDeleteRedirect": "Delete Redirect",
"actionGetRedirect": "Get Redirect",
"actionListRedirects": "List Redirects",
"actionUpdateRedirect": "Update Redirect",
"actionApplyBlueprint": "Apply Blueprint",
"actionListBlueprints": "List Blueprints",
"actionGetBlueprint": "Get Blueprint",
+2
View File
@@ -16,6 +16,7 @@ import {
RemoteExitNode,
Session,
SiteResource,
Redirect,
User,
UserOrg,
VirtualApiKey
@@ -96,6 +97,7 @@ declare global {
aiModel?: AiModel;
aiBudget?: AiBudget;
virtualApiKey?: VirtualApiKey;
redirect?: Redirect;
orgPolicyAllowed?: boolean;
}
}
+1
View File
@@ -22,3 +22,4 @@ export * from "./verifyApiKeyAiBudgetAccess";
export * from "./verifyApiKeyResourcePolicyAccess";
export * from "./verifyApiKeySiteProvisioningKeyAccess";
export * from "./verifyVirtualApiKeyAccess";
export * from "./verifyApiKeyRedirectAccess";
@@ -0,0 +1,116 @@
import { Request, Response, NextFunction } from "express";
import { Redirect, apiKeyOrg, db, redirects } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyApiKeyRedirectAccess(
req: Request,
res: Response,
next: NextFunction
) {
try {
const apiKey = req.apiKey;
const redirectIdRaw = getFirstString(req.params.redirectId);
const niceId = getFirstString(req.params.niceId);
const orgIdParam = getFirstString(req.params.orgId);
if (!apiKey) {
return next(
createHttpError(HttpCode.UNAUTHORIZED, "Key not authenticated")
);
}
if (!orgIdParam) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID")
);
}
let redirect: Redirect | undefined;
if (niceId) {
const [redirectRes] = await db
.select()
.from(redirects)
.where(
and(
eq(redirects.niceId, niceId),
eq(redirects.orgId, orgIdParam)
)
)
.limit(1);
redirect = redirectRes;
} else {
const redirectId = Number.parseInt(redirectIdRaw ?? "", 10);
if (Number.isNaN(redirectId)) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid redirect ID")
);
}
const [redirectRes] = await db
.select()
.from(redirects)
.where(
and(
eq(redirects.redirectId, redirectId),
eq(redirects.orgId, orgIdParam)
)
)
.limit(1);
redirect = redirectRes;
}
if (!redirect) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Redirect with ID ${redirectIdRaw || niceId} not found`
)
);
}
if (apiKey.isRoot) {
req.redirect = redirect;
return next();
}
const orgId = redirect.orgId;
if (!req.apiKeyOrg || req.apiKeyOrg.orgId !== orgId) {
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"
)
);
}
req.redirect = redirect;
return next();
} catch (error) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying redirect access"
)
);
}
}
+51
View File
@@ -16,6 +16,7 @@ import * as siteResource from "./siteResource";
import * as aiProvider from "./aiProvider";
import * as aiBudget from "./aiBudget";
import * as virtualApiKey from "./virtualApiKey";
import * as redirect from "./redirect";
import {
verifyApiKey,
verifyApiKeyOrgAccess,
@@ -38,6 +39,7 @@ import {
verifyApiKeyAiModelAccess,
verifyApiKeyAiBudgetAccess,
verifyApiKeyVirtualApiKeyAccess,
verifyApiKeyRedirectAccess,
verifyUserHasAction
} from "@server/middlewares";
import HttpCode from "@server/types/HttpCode";
@@ -1833,3 +1835,52 @@ authenticated.get(
verifyApiKeyHasAction(ActionsEnum.listAiBudgets),
aiBudget.listAiBudgetsForVirtualApiKey
);
authenticated.put(
"/org/:orgId/redirect",
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.createRedirect),
logActionAudit(ActionsEnum.createRedirect),
redirect.createRedirect
);
authenticated.get(
"/org/:orgId/redirects",
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.listRedirects),
redirect.listRedirects
);
authenticated.get(
"/org/:orgId/redirects/:redirectId",
verifyApiKeyOrgAccess,
verifyApiKeyRedirectAccess,
verifyApiKeyHasAction(ActionsEnum.getRedirect),
redirect.getRedirect
);
authenticated.get(
"/org/:orgId/redirect/:niceId",
verifyApiKeyOrgAccess,
verifyApiKeyRedirectAccess,
verifyApiKeyHasAction(ActionsEnum.getRedirect),
redirect.getRedirect
);
authenticated.post(
"/org/:orgId/redirects/:redirectId",
verifyApiKeyOrgAccess,
verifyApiKeyRedirectAccess,
verifyApiKeyHasAction(ActionsEnum.updateRedirect),
logActionAudit(ActionsEnum.updateRedirect),
redirect.updateRedirect
);
authenticated.delete(
"/org/:orgId/redirects/:redirectId",
verifyApiKeyOrgAccess,
verifyApiKeyRedirectAccess,
verifyApiKeyHasAction(ActionsEnum.deleteRedirect),
logActionAudit(ActionsEnum.deleteRedirect),
redirect.deleteRedirect
);
+8
View File
@@ -49,6 +49,14 @@ function getActionsCategories(root: boolean) {
[t("actionRestartOrgDomain")]: "restartOrgDomain"
},
Redirect: {
[t("actionCreateRedirect")]: "createRedirect",
[t("actionDeleteRedirect")]: "deleteRedirect",
[t("actionGetRedirect")]: "getRedirect",
[t("actionListRedirects")]: "listRedirects",
[t("actionUpdateRedirect")]: "updateRedirect"
},
Site: {
[t("actionCreateSite")]: "createSite",
[t("actionDeleteSite")]: "deleteSite",