Merge branch 'dev' into feat/resource-policies

This commit is contained in:
Fred KISSIE
2026-03-04 16:46:33 +01:00
138 changed files with 469 additions and 727 deletions

View File

@@ -654,11 +654,6 @@ export const resourceRules = pgTable("resourceRules", {
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, { onDelete: "cascade" }),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
}),
enabled: boolean("enabled").notNull().default(true),
priority: integer("priority").notNull(),
action: varchar("action").notNull(), // ACCEPT, DROP, PASS

View File

@@ -17,6 +17,7 @@ import fs from "fs";
import path from "path";
import { APP_PATH } from "./lib/consts";
import yaml from "js-yaml";
import { z } from "zod";
const dev = process.env.ENVIRONMENT !== "prod";
const externalPort = config.getRawConfig().server.integration_port;
@@ -38,12 +39,24 @@ export function createIntegrationApiServer() {
apiServer.use(cookieParser());
apiServer.use(express.json());
const openApiDocumentation = getOpenApiDocumentation();
apiServer.use(
"/v1/docs",
swaggerUi.serve,
swaggerUi.setup(getOpenApiDocumentation())
swaggerUi.setup(openApiDocumentation)
);
// Unauthenticated OpenAPI spec endpoints
apiServer.get("/v1/openapi.json", (_req, res) => {
res.json(openApiDocumentation);
});
apiServer.get("/v1/openapi.yaml", (_req, res) => {
const yamlOutput = yaml.dump(openApiDocumentation);
res.type("application/yaml").send(yamlOutput);
});
// API routes
const prefix = `/v1`;
apiServer.use(logIncomingMiddleware);
@@ -75,16 +88,6 @@ function getOpenApiDocumentation() {
}
);
for (const def of registry.definitions) {
if (def.type === "route") {
def.route.security = [
{
[bearerAuth.name]: []
}
];
}
}
registry.registerPath({
method: "get",
path: "/",
@@ -94,6 +97,74 @@ function getOpenApiDocumentation() {
responses: {}
});
registry.registerPath({
method: "get",
path: "/openapi.json",
description: "Get OpenAPI specification as JSON",
tags: [],
request: {},
responses: {
"200": {
description: "OpenAPI specification as JSON",
content: {
"application/json": {
schema: {
type: "object"
}
}
}
}
}
});
registry.registerPath({
method: "get",
path: "/openapi.yaml",
description: "Get OpenAPI specification as YAML",
tags: [],
request: {},
responses: {
"200": {
description: "OpenAPI specification as YAML",
content: {
"application/yaml": {
schema: {
type: "string"
}
}
}
}
}
});
for (const def of registry.definitions) {
if (def.type === "route") {
def.route.security = [
{
[bearerAuth.name]: []
}
];
// Ensure every route has a generic JSON response schema so Swagger UI can render responses
const existingResponses = def.route.responses;
const hasExistingResponses =
existingResponses && Object.keys(existingResponses).length > 0;
if (!hasExistingResponses) {
def.route.responses = {
"*": {
description: "",
content: {
"application/json": {
schema: z.object({})
}
}
}
};
}
}
}
const generator = new OpenApiGeneratorV3(registry.definitions);
const generated = generator.generateDocument({

View File

@@ -477,7 +477,10 @@ export async function getTraefikConfig(
// TODO: HOW TO HANDLE ^^^^^^ BETTER
const anySitesOnline = targets.some(
(target) => target.site.online
(target) =>
target.site.online ||
target.site.type === "local" ||
target.site.type === "wireguard"
);
return (
@@ -490,7 +493,7 @@ export async function getTraefikConfig(
if (target.health == "unhealthy") {
return false;
}
// If any sites are online, exclude offline sites
if (anySitesOnline && !target.site.online) {
return false;
@@ -605,7 +608,10 @@ export async function getTraefikConfig(
servers: (() => {
// Check if any sites are online
const anySitesOnline = targets.some(
(target) => target.site.online
(target) =>
target.site.online ||
target.site.type === "local" ||
target.site.type === "wireguard"
);
return targets
@@ -613,7 +619,7 @@ export async function getTraefikConfig(
if (!target.enabled) {
return false;
}
// If any sites are online, exclude offline sites
if (anySitesOnline && !target.site.online) {
return false;

View File

@@ -5,18 +5,21 @@ export const registry = new OpenAPIRegistry();
export enum OpenAPITags {
Site = "Site",
Org = "Organization",
Resource = "Resource",
PublicResource = "Public Resource",
PrivateResource = "Private Resource",
Policy = "Policy",
Role = "Role",
User = "User",
Invitation = "Invitation",
Target = "Target",
Invitation = "User Invitation",
Target = "Resource Target",
Rule = "Rule",
AccessToken = "Access Token",
Idp = "Identity Provider",
GlobalIdp = "Identity Provider (Global)",
OrgIdp = "Identity Provider (Organization Only)",
Client = "Client",
ApiKey = "API Key",
Domain = "Domain",
Blueprint = "Blueprint",
Ssh = "SSH"
Ssh = "SSH",
Logs = "Logs"
}

View File

@@ -665,7 +665,10 @@ export async function getTraefikConfig(
// TODO: HOW TO HANDLE ^^^^^^ BETTER
const anySitesOnline = targets.some(
(target) => target.site.online
(target) =>
target.site.online ||
target.site.type === "local" ||
target.site.type === "wireguard"
);
return (
@@ -793,7 +796,10 @@ export async function getTraefikConfig(
servers: (() => {
// Check if any sites are online
const anySitesOnline = targets.some(
(target) => target.site.online
(target) =>
target.site.online ||
target.site.type === "local" ||
target.site.type === "wireguard"
);
return targets

View File

@@ -32,7 +32,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/access/export",
description: "Export the access audit log for an organization as CSV",
tags: [OpenAPITags.Org],
tags: [OpenAPITags.Logs],
request: {
query: queryAccessAuditLogsQuery,
params: queryAccessAuditLogsParams

View File

@@ -32,7 +32,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/action/export",
description: "Export the action audit log for an organization as CSV",
tags: [OpenAPITags.Org],
tags: [OpenAPITags.Logs],
request: {
query: queryActionAuditLogsQuery,
params: queryActionAuditLogsParams

View File

@@ -249,7 +249,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/access",
description: "Query the access audit log for an organization",
tags: [OpenAPITags.Org],
tags: [OpenAPITags.Logs],
request: {
query: queryAccessAuditLogsQuery,
params: queryAccessAuditLogsParams

View File

@@ -160,7 +160,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/action",
description: "Query the action audit log for an organization",
tags: [OpenAPITags.Org],
tags: [OpenAPITags.Logs],
request: {
query: queryActionAuditLogsQuery,
params: queryActionAuditLogsParams

View File

@@ -31,16 +31,16 @@ const getOrgSchema = z.strictObject({
orgId: z.string()
});
registry.registerPath({
method: "get",
path: "/org/{orgId}/billing/usage",
description: "Get an organization's billing usage",
tags: [OpenAPITags.Org],
request: {
params: getOrgSchema
},
responses: {}
});
// registry.registerPath({
// method: "get",
// path: "/org/{orgId}/billing/usage",
// description: "Get an organization's billing usage",
// tags: [OpenAPITags.Org],
// request: {
// params: getOrgSchema
// },
// responses: {}
// });
export async function getOrgUsage(
req: Request,

View File

@@ -35,7 +35,6 @@ import {
verifyUserIsServerAdmin,
verifySiteAccess,
verifyClientAccess,
verifyLimits,
verifyLimits
} from "@server/middlewares";
import { ActionsEnum } from "@server/auth/actions";

View File

@@ -52,7 +52,7 @@ registry.registerPath({
method: "put",
path: "/org/{orgId}/idp/oidc",
description: "Create an OIDC IdP for a specific organization.",
tags: [OpenAPITags.Idp, OpenAPITags.Org],
tags: [OpenAPITags.OrgIdp],
request: {
params: paramsSchema,
body: {

View File

@@ -35,7 +35,7 @@ registry.registerPath({
method: "delete",
path: "/org/{orgId}/idp/{idpId}",
description: "Delete IDP for a specific organization.",
tags: [OpenAPITags.Idp, OpenAPITags.Org],
tags: [OpenAPITags.OrgIdp],
request: {
params: paramsSchema
},

View File

@@ -50,9 +50,9 @@ async function query(idpId: number, orgId: string) {
registry.registerPath({
method: "get",
path: "/org/:orgId/idp/:idpId",
path: "/org/{orgId}/idp/{idpId}",
description: "Get an IDP by its IDP ID for a specific organization.",
tags: [OpenAPITags.Idp, OpenAPITags.Org],
tags: [OpenAPITags.OrgIdp],
request: {
params: paramsSchema
},

View File

@@ -67,7 +67,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/idp",
description: "List all IDP for a specific organization.",
tags: [OpenAPITags.Idp, OpenAPITags.Org],
tags: [OpenAPITags.OrgIdp],
request: {
query: querySchema,
params: paramsSchema

View File

@@ -59,7 +59,7 @@ registry.registerPath({
method: "post",
path: "/org/{orgId}/idp/{idpId}/oidc",
description: "Update an OIDC IdP for a specific organization.",
tags: [OpenAPITags.Idp, OpenAPITags.Org],
tags: [OpenAPITags.OrgIdp],
request: {
params: paramsSchema,
body: {

View File

@@ -60,7 +60,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/resource-policies",
description: "List resource policies for an organization.",
tags: [OpenAPITags.Org, OpenAPITags.Resource],
tags: [OpenAPITags.Org, OpenAPITags.Policy],
request: {
params: z.object({
orgId: z.string()

View File

@@ -52,7 +52,7 @@ registry.registerPath({
method: "get",
path: "/maintenance/info",
description: "Get maintenance information for a resource by domain.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
query: z.object({
fullDomain: z.string()

View File

@@ -43,7 +43,7 @@ registry.registerPath({
method: "post",
path: "/resource/{resourceId}/access-token",
description: "Generate a new access token for a resource.",
tags: [OpenAPITags.Resource, OpenAPITags.AccessToken],
tags: [OpenAPITags.PublicResource, OpenAPITags.AccessToken],
request: {
params: generateAccssTokenParamsSchema,
body: {

View File

@@ -122,7 +122,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/access-tokens",
description: "List all access tokens in an organization.",
tags: [OpenAPITags.Org, OpenAPITags.AccessToken],
tags: [OpenAPITags.AccessToken],
request: {
params: z.object({
orgId: z.string()
@@ -135,8 +135,8 @@ registry.registerPath({
registry.registerPath({
method: "get",
path: "/resource/{resourceId}/access-tokens",
description: "List all access tokens in an organization.",
tags: [OpenAPITags.Resource, OpenAPITags.AccessToken],
description: "List all access tokens for a resource.",
tags: [OpenAPITags.PublicResource, OpenAPITags.AccessToken],
request: {
params: z.object({
resourceId: z.number()

View File

@@ -37,7 +37,7 @@ registry.registerPath({
method: "put",
path: "/org/{orgId}/api-key",
description: "Create a new API key scoped to the organization.",
tags: [OpenAPITags.Org, OpenAPITags.ApiKey],
tags: [OpenAPITags.ApiKey],
request: {
params: paramsSchema,
body: {

View File

@@ -18,7 +18,7 @@ registry.registerPath({
method: "delete",
path: "/org/{orgId}/api-key/{apiKeyId}",
description: "Delete an API key.",
tags: [OpenAPITags.Org, OpenAPITags.ApiKey],
tags: [OpenAPITags.ApiKey],
request: {
params: paramsSchema
},

View File

@@ -48,7 +48,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/api-key/{apiKeyId}/actions",
description: "List all actions set for an API key.",
tags: [OpenAPITags.Org, OpenAPITags.ApiKey],
tags: [OpenAPITags.ApiKey],
request: {
params: paramsSchema,
query: querySchema

View File

@@ -52,7 +52,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/api-keys",
description: "List all API keys for an organization",
tags: [OpenAPITags.Org, OpenAPITags.ApiKey],
tags: [OpenAPITags.ApiKey],
request: {
params: paramsSchema,
query: querySchema

View File

@@ -25,7 +25,7 @@ registry.registerPath({
path: "/org/{orgId}/api-key/{apiKeyId}/actions",
description:
"Set actions for an API key. This will replace any existing actions.",
tags: [OpenAPITags.Org, OpenAPITags.ApiKey],
tags: [OpenAPITags.ApiKey],
request: {
params: paramsSchema,
body: {

View File

@@ -20,7 +20,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/request",
description: "Query the request audit log for an organization",
tags: [OpenAPITags.Org],
tags: [OpenAPITags.Logs],
request: {
query: queryAccessAuditLogsQuery.omit({
limit: true,

View File

@@ -151,7 +151,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/analytics",
description: "Query the request audit analytics for an organization",
tags: [OpenAPITags.Org],
tags: [OpenAPITags.Logs],
request: {
query: queryAccessAuditLogsQuery,
params: queryRequestAuditLogsParams

View File

@@ -182,7 +182,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/request",
description: "Query the request audit log for an organization",
tags: [OpenAPITags.Org],
tags: [OpenAPITags.Logs],
request: {
query: queryAccessAuditLogsQuery,
params: queryRequestAuditLogsParams

View File

@@ -20,7 +20,7 @@ registry.registerPath({
method: "put",
path: "/org/{orgId}/blueprint",
description: "Apply a base64 encoded JSON blueprint to an organization",
tags: [OpenAPITags.Org, OpenAPITags.Blueprint],
tags: [OpenAPITags.Blueprint],
request: {
params: applyBlueprintParamsSchema,
body: {

View File

@@ -43,7 +43,7 @@ registry.registerPath({
method: "put",
path: "/org/{orgId}/blueprint",
description: "Create and apply a YAML blueprint to an organization",
tags: [OpenAPITags.Org, OpenAPITags.Blueprint],
tags: [OpenAPITags.Blueprint],
request: {
params: applyBlueprintParamsSchema,
body: {

View File

@@ -53,7 +53,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/blueprint/{blueprintId}",
description: "Get a blueprint by its blueprint ID.",
tags: [OpenAPITags.Org, OpenAPITags.Blueprint],
tags: [OpenAPITags.Blueprint],
request: {
params: getBlueprintSchema
},

View File

@@ -67,7 +67,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/blueprints",
description: "List all blueprints for a organization.",
tags: [OpenAPITags.Org, OpenAPITags.Blueprint],
tags: [OpenAPITags.Blueprint],
request: {
params: z.object({
orgId: z.string()

View File

@@ -48,7 +48,7 @@ registry.registerPath({
method: "put",
path: "/org/{orgId}/client",
description: "Create a new client for an organization.",
tags: [OpenAPITags.Client, OpenAPITags.Org],
tags: [OpenAPITags.Client],
request: {
params: createClientParamsSchema,
body: {

View File

@@ -49,7 +49,7 @@ registry.registerPath({
path: "/org/{orgId}/user/{userId}/client",
description:
"Create a new client for a user and associate it with an existing olm.",
tags: [OpenAPITags.Client, OpenAPITags.Org, OpenAPITags.User],
tags: [OpenAPITags.Client],
request: {
params: paramsSchema,
body: {

View File

@@ -243,7 +243,7 @@ registry.registerPath({
path: "/org/{orgId}/client/{niceId}",
description:
"Get a client by orgId and niceId. NiceId is a readable ID for the site and unique on a per org basis.",
tags: [OpenAPITags.Org, OpenAPITags.Site],
tags: [OpenAPITags.Site],
request: {
params: z.object({
orgId: z.string(),

View File

@@ -237,7 +237,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/clients",
description: "List all clients for an organization.",
tags: [OpenAPITags.Client, OpenAPITags.Org],
tags: [OpenAPITags.Client],
request: {
query: listClientsSchema,
params: listClientsParamsSchema

View File

@@ -256,7 +256,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/user-devices",
description: "List all user devices for an organization.",
tags: [OpenAPITags.Client, OpenAPITags.Org],
tags: [OpenAPITags.Client],
request: {
query: listUserDevicesSchema,
params: listUserDevicesParamsSchema

View File

@@ -23,7 +23,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/pick-client-defaults",
description: "Return pre-requisite data for creating a client.",
tags: [OpenAPITags.Client, OpenAPITags.Site],
tags: [OpenAPITags.Client],
request: {
params: pickClientDefaultsSchema
},

View File

@@ -59,7 +59,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/domains",
description: "List all domains for a organization.",
tags: [OpenAPITags.Org],
tags: [OpenAPITags.Domain],
request: {
params: z.object({
orgId: z.string()

View File

@@ -693,13 +693,6 @@ authenticated.post(
resource.setResourceUsers
);
authenticated.get(
"/resource-policy/:resourcePolicyId/roles",
verifyResourcePolicyAccess,
verifyUserHasAction(ActionsEnum.listResourcePolicyRoles),
resource.listResourcePolicyRoles
);
authenticated.put(
"/resource-policy/:resourcePolicyId/access-control",
verifyResourcePolicyAccess,
@@ -708,32 +701,6 @@ authenticated.put(
policy.setResourcePolicyAccessControl
);
authenticated.get(
"/resource-policy/:resourcePolicyId/users",
verifyResourcePolicyAccess,
verifyUserHasAction(ActionsEnum.listResourcePolicyUsers),
resource.listResourcePolicyUsers
);
authenticated.post(
"/resource-policy/:resourcePolicyId/roles",
verifyResourcePolicyAccess,
verifyRoleAccess,
verifyLimits,
verifyUserHasAction(ActionsEnum.setResourcePolicyRoles),
logActionAudit(ActionsEnum.setResourcePolicyRoles),
resource.setResourcePolicyRoles
);
authenticated.post(
"/resource-policy/:resourcePolicyId/users",
verifyResourcePolicyAccess,
verifyLimits,
verifyUserHasAction(ActionsEnum.setResourcePolicyUsers),
logActionAudit(ActionsEnum.setResourcePolicyUsers),
resource.setResourcePolicyUsers
);
authenticated.put(
"/resource-policy/:resourcePolicyId/password",
verifyResourcePolicyAccess,

View File

@@ -27,7 +27,7 @@ registry.registerPath({
method: "put",
path: "/idp/{idpId}/org/{orgId}",
description: "Create an IDP policy for an existing IDP on an organization.",
tags: [OpenAPITags.Idp],
tags: [OpenAPITags.GlobalIdp],
request: {
params: paramsSchema,
body: {

View File

@@ -37,7 +37,7 @@ registry.registerPath({
method: "put",
path: "/idp/oidc",
description: "Create an OIDC IdP.",
tags: [OpenAPITags.Idp],
tags: [OpenAPITags.GlobalIdp],
request: {
body: {
content: {

View File

@@ -21,7 +21,7 @@ registry.registerPath({
method: "delete",
path: "/idp/{idpId}",
description: "Delete IDP.",
tags: [OpenAPITags.Idp],
tags: [OpenAPITags.GlobalIdp],
request: {
params: paramsSchema
},

View File

@@ -19,7 +19,7 @@ registry.registerPath({
method: "delete",
path: "/idp/{idpId}/org/{orgId}",
description: "Create an OIDC IdP for an organization.",
tags: [OpenAPITags.Idp],
tags: [OpenAPITags.GlobalIdp],
request: {
params: paramsSchema
},

View File

@@ -34,7 +34,7 @@ registry.registerPath({
method: "get",
path: "/idp/{idpId}",
description: "Get an IDP by its IDP ID.",
tags: [OpenAPITags.Idp],
tags: [OpenAPITags.GlobalIdp],
request: {
params: paramsSchema
},

View File

@@ -48,7 +48,7 @@ registry.registerPath({
method: "get",
path: "/idp/{idpId}/org",
description: "List all org policies on an IDP.",
tags: [OpenAPITags.Idp],
tags: [OpenAPITags.GlobalIdp],
request: {
params: paramsSchema,
query: querySchema

View File

@@ -58,7 +58,7 @@ registry.registerPath({
method: "get",
path: "/idp",
description: "List all IDP in the system.",
tags: [OpenAPITags.Idp],
tags: [OpenAPITags.GlobalIdp],
request: {
query: querySchema
},

View File

@@ -26,7 +26,7 @@ registry.registerPath({
method: "post",
path: "/idp/{idpId}/org/{orgId}",
description: "Update an IDP org policy.",
tags: [OpenAPITags.Idp],
tags: [OpenAPITags.GlobalIdp],
request: {
params: paramsSchema,
body: {

View File

@@ -42,7 +42,7 @@ registry.registerPath({
method: "post",
path: "/idp/{idpId}/oidc",
description: "Update an OIDC IdP.",
tags: [OpenAPITags.Idp],
tags: [OpenAPITags.GlobalIdp],
request: {
params: paramsSchema,
body: {

View File

@@ -230,7 +230,7 @@ export async function buildTargetConfigurationForNewtClient(siteId: number) {
!target.hcMethod
) {
logger.debug(
`Skipping target ${target.targetId} due to missing health check fields`
`Skipping adding target health check ${target.targetId} due to missing health check fields`
);
return null; // Skip targets with missing health check fields
}

View File

@@ -35,7 +35,7 @@ registry.registerPath({
path: "/resource-policy/{resourceId}/access-control",
description:
"Set access control users for a resource policy, including SSO, users, roles, Identity provider.",
tags: [OpenAPITags.Resource, OpenAPITags.User],
tags: [OpenAPITags.Policy, OpenAPITags.User],
request: {
params: setResourcePolicyAccessControlParamsSchema,
body: {

View File

@@ -29,7 +29,7 @@ registry.registerPath({
path: "/resource-policy/{resourcePolicyId}/header-auth",
description:
"Set or update the header authentication for a resource policy. If user and password is not provided, it will remove the header authentication.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.Policy],
request: {
params: setResourcePolicyHeaderAuthParamsSchema,
body: {

View File

@@ -24,7 +24,7 @@ registry.registerPath({
path: "/resource-policy/{resourcePolicyId}/password",
description:
"Set the password for a resource policy. Setting the password to null will remove it.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.Policy],
request: {
params: setResourcePolicyPasswordParamsSchema,
body: {

View File

@@ -27,7 +27,7 @@ registry.registerPath({
path: "/resource-policy/{resourcePolicyId}/pincode",
description:
"Set the PIN code for a resource policy. Setting the PIN code to null will remove it.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.Policy],
request: {
params: setResourcePolicyPincodeParamsSchema,
body: {
@@ -78,10 +78,7 @@ export async function setResourcePolicyPincode(
await trx
.delete(resourcePolicyPincode)
.where(
eq(
resourcePolicyPincode.resourcePolicyId,
resourcePolicyId
)
eq(resourcePolicyPincode.resourcePolicyId, resourcePolicyId)
);
if (pincode) {

View File

@@ -32,7 +32,7 @@ registry.registerPath({
path: "/resource-policy/{resourcePolicyId}/whitelist",
description:
"Set email whitelist for a resource policy. This will replace all existing emails.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.Policy],
request: {
params: setResourcePolicyWhitelistParamsSchema,
body: {

View File

@@ -29,7 +29,7 @@ registry.registerPath({
method: "post",
path: "/resource/{resourceId}/whitelist/add",
description: "Add a single email to the resource whitelist.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: addEmailToResourceWhitelistParamsSchema,
body: {

View File

@@ -29,7 +29,7 @@ registry.registerPath({
method: "post",
path: "/resource/{resourceId}/roles/add",
description: "Add a single role to a resource.",
tags: [OpenAPITags.Resource, OpenAPITags.Role],
tags: [OpenAPITags.PublicResource, OpenAPITags.Role],
request: {
params: addRoleToResourceParamsSchema,
body: {

View File

@@ -29,7 +29,7 @@ registry.registerPath({
method: "post",
path: "/resource/{resourceId}/users/add",
description: "Add a single user to a resource.",
tags: [OpenAPITags.Resource, OpenAPITags.User],
tags: [OpenAPITags.PublicResource, OpenAPITags.User],
request: {
params: addUserToResourceParamsSchema,
body: {

View File

@@ -78,7 +78,7 @@ registry.registerPath({
method: "put",
path: "/org/{orgId}/resource",
description: "Create a resource.",
tags: [OpenAPITags.Org, OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: createResourceParamsSchema,
body: {

View File

@@ -31,7 +31,7 @@ registry.registerPath({
method: "put",
path: "/resource/{resourceId}/rule",
description: "Create a resource rule.",
tags: [OpenAPITags.Resource, OpenAPITags.Rule],
tags: [OpenAPITags.PublicResource, OpenAPITags.Rule],
request: {
params: createResourceRuleParamsSchema,
body: {

View File

@@ -22,7 +22,7 @@ registry.registerPath({
method: "delete",
path: "/resource/{resourceId}",
description: "Delete a resource.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: deleteResourceSchema
},

View File

@@ -19,7 +19,7 @@ registry.registerPath({
method: "delete",
path: "/resource/{resourceId}/rule/{ruleId}",
description: "Delete a resource rule.",
tags: [OpenAPITags.Resource, OpenAPITags.Rule],
tags: [OpenAPITags.PublicResource, OpenAPITags.Rule],
request: {
params: deleteResourceRuleSchema
},

View File

@@ -54,7 +54,7 @@ registry.registerPath({
path: "/org/{orgId}/resource/{niceId}",
description:
"Get a resource by orgId and niceId. NiceId is a readable ID for the resource and unique on a per org basis.",
tags: [OpenAPITags.Org, OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: z.object({
orgId: z.string(),
@@ -68,7 +68,7 @@ registry.registerPath({
method: "get",
path: "/resource/{resourceId}",
description: "Get a resource by resourceId.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: z.object({
resourceId: z.number()

View File

@@ -31,7 +31,7 @@ registry.registerPath({
method: "get",
path: "/resource/{resourceId}/whitelist",
description: "Get the whitelist of emails for a specific resource.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: getResourceWhitelistSchema
},

View File

@@ -31,7 +31,3 @@ export * from "./addUserToResource";
export * from "./removeUserFromResource";
export * from "./listAllResourceNames";
export * from "./removeEmailFromResourceWhitelist";
export * from "./listResourcePolicyRoles";
export * from "./listResourcePolicyUsers";
export * from "./setResourcePolicyRoles";
export * from "./setResourcePolicyUsers";

View File

@@ -33,7 +33,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/resources-names",
description: "List all resource names for an organization.",
tags: [OpenAPITags.Org, OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: z.object({
orgId: z.string()

View File

@@ -1,80 +0,0 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import { roleResources, roles } from "@server/db";
import { eq } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const listResourcePolicyRolesSchema = z.strictObject({
resourcePolicyId: z.string().transform(Number).pipe(z.int().positive())
});
async function query(resourcePolicyId: number) {
return await db
.selectDistinct({
roleId: roles.roleId,
name: roles.name,
description: roles.description,
isAdmin: roles.isAdmin
})
.from(roleResources)
.innerJoin(roles, eq(roleResources.roleId, roles.roleId))
.where(eq(roleResources.resourcePolicyId, resourcePolicyId));
}
export type ListResourcePolicyRolesResponse = {
roles: NonNullable<Awaited<ReturnType<typeof query>>>;
};
registry.registerPath({
method: "get",
path: "/resource-policy/{resourcePolicyId}/roles",
description: "List all roles for a resource policy.",
tags: [OpenAPITags.Resource, OpenAPITags.Role],
request: {
params: listResourcePolicyRolesSchema
},
responses: {}
});
export async function listResourcePolicyRoles(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = listResourcePolicyRolesSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { resourcePolicyId } = parsedParams.data;
const policyRolesList = await query(resourcePolicyId);
return response<ListResourcePolicyRolesResponse>(res, {
data: {
roles: policyRolesList
},
success: true,
error: false,
message: "Resource policy roles retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -1,85 +0,0 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import { idp, userResources, users } from "@server/db";
import { eq } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const listResourcePolicyUsersSchema = z.strictObject({
resourcePolicyId: z.string().transform(Number).pipe(z.int().positive())
});
async function queryUsers(resourcePolicyId: number) {
return await db
.selectDistinct({
userId: userResources.userId,
username: users.username,
type: users.type,
idpName: idp.name,
idpId: users.idpId,
email: users.email
})
.from(userResources)
.innerJoin(users, eq(userResources.userId, users.userId))
.leftJoin(idp, eq(users.idpId, idp.idpId))
.where(eq(userResources.resourcePolicyId, resourcePolicyId));
}
export type ListResourcePolicyUsersResponse = {
users: NonNullable<Awaited<ReturnType<typeof queryUsers>>>;
};
registry.registerPath({
method: "get",
path: "/resource-policy/{resourcePolicyId}/users",
description: "List all users for a resource policy.",
tags: [OpenAPITags.Resource, OpenAPITags.User],
request: {
params: listResourcePolicyUsersSchema
},
responses: {}
});
export async function listResourcePolicyUsers(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = listResourcePolicyUsersSchema.safeParse(
req.params
);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { resourcePolicyId } = parsedParams.data;
const policyUsersList = await queryUsers(resourcePolicyId);
return response<ListResourcePolicyUsersResponse>(res, {
data: {
users: policyUsersList
},
success: true,
error: false,
message: "Resource policy users retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -35,7 +35,7 @@ registry.registerPath({
method: "get",
path: "/resource/{resourceId}/roles",
description: "List all roles for a resource.",
tags: [OpenAPITags.Resource, OpenAPITags.Role],
tags: [OpenAPITags.PublicResource, OpenAPITags.Role],
request: {
params: listResourceRolesSchema
},

View File

@@ -56,7 +56,7 @@ registry.registerPath({
method: "get",
path: "/resource/{resourceId}/rules",
description: "List rules for a resource.",
tags: [OpenAPITags.Resource, OpenAPITags.Rule],
tags: [OpenAPITags.PublicResource, OpenAPITags.Rule],
request: {
params: listResourceRulesParamsSchema,
query: listResourceRulesSchema

View File

@@ -38,7 +38,7 @@ registry.registerPath({
method: "get",
path: "/resource/{resourceId}/users",
description: "List all users for a resource.",
tags: [OpenAPITags.Resource, OpenAPITags.User],
tags: [OpenAPITags.PublicResource, OpenAPITags.User],
request: {
params: listResourceUsersSchema
},

View File

@@ -225,7 +225,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/resources",
description: "List resources for an organization.",
tags: [OpenAPITags.Org, OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: z.object({
orgId: z.string()

View File

@@ -29,7 +29,7 @@ registry.registerPath({
method: "post",
path: "/resource/{resourceId}/whitelist/remove",
description: "Remove a single email from the resource whitelist.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: removeEmailFromResourceWhitelistParamsSchema,
body: {

View File

@@ -29,7 +29,7 @@ registry.registerPath({
method: "post",
path: "/resource/{resourceId}/roles/remove",
description: "Remove a single role from a resource.",
tags: [OpenAPITags.Resource, OpenAPITags.Role],
tags: [OpenAPITags.PublicResource, OpenAPITags.Role],
request: {
params: removeRoleFromResourceParamsSchema,
body: {

View File

@@ -29,7 +29,7 @@ registry.registerPath({
method: "post",
path: "/resource/{resourceId}/users/remove",
description: "Remove a single user from a resource.",
tags: [OpenAPITags.Resource, OpenAPITags.User],
tags: [OpenAPITags.PublicResource, OpenAPITags.User],
request: {
params: removeUserFromResourceParamsSchema,
body: {

View File

@@ -29,7 +29,7 @@ registry.registerPath({
path: "/resource/{resourceId}/header-auth",
description:
"Set or update the header authentication for a resource. If user and password is not provided, it will remove the header authentication.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: setResourceAuthMethodsParamsSchema,
body: {

View File

@@ -25,7 +25,7 @@ registry.registerPath({
path: "/resource/{resourceId}/password",
description:
"Set the password for a resource. Setting the password to null will remove it.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: setResourceAuthMethodsParamsSchema,
body: {

View File

@@ -29,7 +29,7 @@ registry.registerPath({
path: "/resource/{resourceId}/pincode",
description:
"Set the PIN code for a resource. Setting the PIN code to null will remove it.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: setResourceAuthMethodsParamsSchema,
body: {

View File

@@ -1,165 +0,0 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import { resourcePolicies, resources, roleResources, roles } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and, ne, inArray } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
const setResourcePolicyRolesBodySchema = z.strictObject({
roleIds: z.array(z.int().positive())
});
const setResourcePolicyRolesParamsSchema = z.strictObject({
resourcePolicyId: z.string().transform(Number).pipe(z.int().positive())
});
registry.registerPath({
method: "post",
path: "/resource-policy/{resourcePolicyId}/roles",
description:
"Set roles for a resource policy. This will replace all existing roles across all resources under this policy.",
tags: [OpenAPITags.Resource, OpenAPITags.Role],
request: {
params: setResourcePolicyRolesParamsSchema,
body: {
content: {
"application/json": {
schema: setResourcePolicyRolesBodySchema
}
}
}
},
responses: {}
});
export async function setResourcePolicyRoles(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedBody = setResourcePolicyRolesBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { roleIds } = parsedBody.data;
const parsedParams = setResourcePolicyRolesParamsSchema.safeParse(
req.params
);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { resourcePolicyId } = parsedParams.data;
const [policy] = await db
.select()
.from(resourcePolicies)
.where(eq(resourcePolicies.resourcePolicyId, resourcePolicyId))
.limit(1);
if (!policy) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource policy not found")
);
}
// Check if any of the roleIds are admin roles
const rolesToCheck = await db
.select()
.from(roles)
.where(
and(
inArray(roles.roleId, roleIds),
eq(roles.orgId, policy.orgId)
)
);
const hasAdminRole = rolesToCheck.some((role) => role.isAdmin);
if (hasAdminRole) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Admin role cannot be assigned to resource policies"
)
);
}
// Get admin role IDs for this org to exclude from deletion
const adminRoles = await db
.select()
.from(roles)
.where(
and(eq(roles.isAdmin, true), eq(roles.orgId, policy.orgId))
);
const adminRoleIds = adminRoles.map((role) => role.roleId);
// Get all resources under this policy
const policyResources = await db
.select({ resourceId: resources.resourceId })
.from(resources)
.where(eq(resources.resourcePolicyId, resourcePolicyId));
await db.transaction(async (trx) => {
// Delete existing role associations for this policy (excluding admin roles)
if (adminRoleIds.length > 0) {
await trx.delete(roleResources).where(
and(
eq(roleResources.resourcePolicyId, resourcePolicyId),
ne(roleResources.roleId, adminRoleIds[0])
)
);
} else {
await trx
.delete(roleResources)
.where(
eq(roleResources.resourcePolicyId, resourcePolicyId)
);
}
// Insert new role associations for each resource under the policy
if (roleIds.length > 0 && policyResources.length > 0) {
await Promise.all(
policyResources.flatMap(({ resourceId }) =>
roleIds.map((roleId) =>
trx
.insert(roleResources)
.values({ roleId, resourceId, resourcePolicyId })
.returning()
)
)
);
}
return response(res, {
data: {},
success: true,
error: false,
message: "Roles set for resource policy successfully",
status: HttpCode.CREATED
});
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -1,124 +0,0 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import { resourcePolicies, resources, userResources } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
const setResourcePolicyUsersBodySchema = z.strictObject({
userIds: z.array(z.string())
});
const setResourcePolicyUsersParamsSchema = z.strictObject({
resourcePolicyId: z.string().transform(Number).pipe(z.int().positive())
});
registry.registerPath({
method: "post",
path: "/resource-policy/{resourcePolicyId}/users",
description:
"Set users for a resource policy. This will replace all existing users across all resources under this policy.",
tags: [OpenAPITags.Resource, OpenAPITags.User],
request: {
params: setResourcePolicyUsersParamsSchema,
body: {
content: {
"application/json": {
schema: setResourcePolicyUsersBodySchema
}
}
}
},
responses: {}
});
export async function setResourcePolicyUsers(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedBody = setResourcePolicyUsersBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { userIds } = parsedBody.data;
const parsedParams = setResourcePolicyUsersParamsSchema.safeParse(
req.params
);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { resourcePolicyId } = parsedParams.data;
const [policy] = await db
.select()
.from(resourcePolicies)
.where(eq(resourcePolicies.resourcePolicyId, resourcePolicyId))
.limit(1);
if (!policy) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource policy not found")
);
}
// Get all resources under this policy
const policyResources = await db
.select({ resourceId: resources.resourceId })
.from(resources)
.where(eq(resources.resourcePolicyId, resourcePolicyId));
await db.transaction(async (trx) => {
// Delete existing user associations for this policy
await trx
.delete(userResources)
.where(eq(userResources.resourcePolicyId, resourcePolicyId));
// Insert new user associations for each resource under the policy
if (userIds.length > 0 && policyResources.length > 0) {
await Promise.all(
policyResources.flatMap(({ resourceId }) =>
userIds.map((userId) =>
trx
.insert(userResources)
.values({ userId, resourceId, resourcePolicyId })
.returning()
)
)
);
}
return response(res, {
data: {},
success: true,
error: false,
message: "Users set for resource policy successfully",
status: HttpCode.CREATED
});
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -23,7 +23,7 @@ registry.registerPath({
path: "/resource/{resourceId}/roles",
description:
"Set roles for a resource. This will replace all existing roles.",
tags: [OpenAPITags.Resource, OpenAPITags.Role],
tags: [OpenAPITags.PublicResource, OpenAPITags.Role],
request: {
params: setResourceRolesParamsSchema,
body: {

View File

@@ -23,7 +23,7 @@ registry.registerPath({
path: "/resource/{resourceId}/users",
description:
"Set users for a resource. This will replace all existing users.",
tags: [OpenAPITags.Resource, OpenAPITags.User],
tags: [OpenAPITags.PublicResource, OpenAPITags.User],
request: {
params: setUserResourcesParamsSchema,
body: {

View File

@@ -32,7 +32,7 @@ registry.registerPath({
path: "/resource/{resourceId}/whitelist",
description:
"Set email whitelist for a resource. This will replace all existing emails.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: setResourceWhitelistParamsSchema,
body: {

View File

@@ -101,6 +101,49 @@ const updateHttpResourceBodySchema = z
{
error: "Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header."
}
)
.refine(
(data) => {
if (data.headers) {
// HTTP header names must be valid token characters (RFC 7230)
const validHeaderName = /^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/;
return data.headers.every((h) => validHeaderName.test(h.name));
}
return true;
},
{
error: "Header names may only contain valid HTTP token characters (letters, digits, and !#$%&'*+-.^_`|~)."
}
)
.refine(
(data) => {
if (data.headers) {
// HTTP header values must be visible ASCII or horizontal whitespace, no control chars (RFC 7230)
const validHeaderValue = /^[\t\x20-\x7E]*$/;
return data.headers.every((h) => validHeaderValue.test(h.value));
}
return true;
},
{
error: "Header values may only contain printable ASCII characters and horizontal whitespace."
}
)
.refine(
(data) => {
if (data.headers) {
// Reject Traefik template syntax {{word}} in names or values
const templatePattern = /\{\{[^}]+\}\}/;
return data.headers.every(
(h) =>
!templatePattern.test(h.name) &&
!templatePattern.test(h.value)
);
}
return true;
},
{
error: "Header names and values must not contain template expressions such as {{value}}."
}
);
export type UpdateResourceResponse = Resource;
@@ -136,7 +179,7 @@ registry.registerPath({
method: "post",
path: "/resource/{resourceId}",
description: "Update a resource.",
tags: [OpenAPITags.Resource],
tags: [OpenAPITags.PublicResource],
request: {
params: updateResourceParamsSchema,
body: {

View File

@@ -38,7 +38,7 @@ registry.registerPath({
method: "post",
path: "/resource/{resourceId}/rule/{ruleId}",
description: "Update a resource rule.",
tags: [OpenAPITags.Resource, OpenAPITags.Rule],
tags: [OpenAPITags.PublicResource, OpenAPITags.Rule],
request: {
params: updateResourceRuleParamsSchema,
body: {

View File

@@ -45,7 +45,7 @@ registry.registerPath({
method: "put",
path: "/org/{orgId}/role",
description: "Create a role.",
tags: [OpenAPITags.Org, OpenAPITags.Role],
tags: [OpenAPITags.Role],
request: {
params: createRoleParamsSchema,
body: {

View File

@@ -7,7 +7,7 @@ import { and, eq, inArray, sql } from "drizzle-orm";
import { ActionsEnum } from "@server/auth/actions";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
import { object, z } from "zod";
import { fromError } from "zod-validation-error";
const listRolesParamsSchema = z.strictObject({
@@ -64,7 +64,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/roles",
description: "List roles.",
tags: [OpenAPITags.Org, OpenAPITags.Role],
tags: [OpenAPITags.Role],
request: {
params: listRolesParamsSchema,
query: listRolesSchema

View File

@@ -58,7 +58,7 @@ registry.registerPath({
method: "put",
path: "/org/{orgId}/site",
description: "Create a new site.",
tags: [OpenAPITags.Site, OpenAPITags.Org],
tags: [OpenAPITags.Site],
request: {
params: createSiteParamsSchema,
body: {
@@ -292,7 +292,7 @@ export async function createSite(
if (type == "newt") {
[newSite] = await trx
.insert(sites)
.values({
.values({ // NOTE: NO SUBNET OR EXIT NODE ID PASSED IN HERE BECAUSE ITS NOW CHOSEN ON CONNECT
orgId,
name,
niceId,

View File

@@ -51,7 +51,7 @@ registry.registerPath({
path: "/org/{orgId}/site/{niceId}",
description:
"Get a site by orgId and niceId. NiceId is a readable ID for the site and unique on a per org basis.",
tags: [OpenAPITags.Org, OpenAPITags.Site],
tags: [OpenAPITags.Site],
request: {
params: z.object({
orgId: z.string(),

View File

@@ -180,7 +180,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/sites",
description: "List all sites in an organization",
tags: [OpenAPITags.Org, OpenAPITags.Site],
tags: [OpenAPITags.Site],
request: {
params: listSitesParamsSchema,
query: listSitesSchema

View File

@@ -35,7 +35,7 @@ registry.registerPath({
path: "/org/{orgId}/pick-site-defaults",
description:
"Return pre-requisite data for creating a site, such as the exit node, subnet, Newt credentials, etc.",
tags: [OpenAPITags.Org, OpenAPITags.Site],
tags: [OpenAPITags.Site],
request: {
params: z.object({
orgId: z.string()

View File

@@ -30,7 +30,7 @@ registry.registerPath({
path: "/site-resource/{siteResourceId}/clients/add",
description:
"Add a single client to a site resource. Clients with a userId cannot be added.",
tags: [OpenAPITags.Resource, OpenAPITags.Client],
tags: [OpenAPITags.PrivateResource, OpenAPITags.Client],
request: {
params: addClientToSiteResourceParamsSchema,
body: {

View File

@@ -30,7 +30,7 @@ registry.registerPath({
method: "post",
path: "/site-resource/{siteResourceId}/roles/add",
description: "Add a single role to a site resource.",
tags: [OpenAPITags.Resource, OpenAPITags.Role],
tags: [OpenAPITags.PrivateResource, OpenAPITags.Role],
request: {
params: addRoleToSiteResourceParamsSchema,
body: {

View File

@@ -30,7 +30,7 @@ registry.registerPath({
method: "post",
path: "/site-resource/{siteResourceId}/users/add",
description: "Add a single user to a site resource.",
tags: [OpenAPITags.Resource, OpenAPITags.User],
tags: [OpenAPITags.PrivateResource, OpenAPITags.User],
request: {
params: addUserToSiteResourceParamsSchema,
body: {

View File

@@ -114,7 +114,7 @@ registry.registerPath({
method: "put",
path: "/org/{orgId}/site-resource",
description: "Create a new site resource.",
tags: [OpenAPITags.Client, OpenAPITags.Org],
tags: [OpenAPITags.PrivateResource],
request: {
params: createSiteResourceParamsSchema,
body: {

View File

@@ -23,7 +23,7 @@ registry.registerPath({
method: "delete",
path: "/site-resource/{siteResourceId}",
description: "Delete a site resource.",
tags: [OpenAPITags.Client, OpenAPITags.Org],
tags: [OpenAPITags.PrivateResource],
request: {
params: deleteSiteResourceParamsSchema
},

View File

@@ -65,7 +65,7 @@ registry.registerPath({
method: "get",
path: "/site-resource/{siteResourceId}",
description: "Get a specific site resource by siteResourceId.",
tags: [OpenAPITags.Client, OpenAPITags.Org],
tags: [OpenAPITags.PrivateResource],
request: {
params: z.object({
siteResourceId: z.number(),
@@ -80,7 +80,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/site/{siteId}/resource/nice/{niceId}",
description: "Get a specific site resource by niceId.",
tags: [OpenAPITags.Client, OpenAPITags.Org],
tags: [OpenAPITags.PrivateResource],
request: {
params: z.object({
niceId: z.string(),

View File

@@ -112,7 +112,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/site-resources",
description: "List all site resources for an organization.",
tags: [OpenAPITags.Client, OpenAPITags.Org],
tags: [OpenAPITags.PrivateResource],
request: {
params: listAllSiteResourcesByOrgParamsSchema,
query: listAllSiteResourcesByOrgQuerySchema

View File

@@ -39,7 +39,7 @@ registry.registerPath({
method: "get",
path: "/site-resource/{siteResourceId}/clients",
description: "List all clients for a site resource.",
tags: [OpenAPITags.Resource, OpenAPITags.Client],
tags: [OpenAPITags.PrivateResource, OpenAPITags.Client],
request: {
params: listSiteResourceClientsSchema
},

View File

@@ -40,7 +40,7 @@ registry.registerPath({
method: "get",
path: "/site-resource/{siteResourceId}/roles",
description: "List all roles for a site resource.",
tags: [OpenAPITags.Resource, OpenAPITags.Role],
tags: [OpenAPITags.PrivateResource, OpenAPITags.Role],
request: {
params: listSiteResourceRolesSchema
},

Some files were not shown because too many files have changed in this diff Show More