Compare commits

..

29 Commits

Author SHA1 Message Date
miloschwartz
4cab693cfc openapi and swagger ui improvements and cleanup 2026-03-03 14:54:17 -08:00
Owen
c9515ae77c Add comment about not needing exit node 2026-03-03 14:54:17 -08:00
miloschwartz
d14de86f65 fix org selector spacing on mobile 2026-03-03 14:54:17 -08:00
Laurence
f6ee9db730 enhance(sidebar): make mobile org selector sticky
Make org selector sticky on mobile sidebar

  Move OrgSelector outside the scrollable container so it stays fixed
  at the top while menu items scroll, matching the desktop sidebar
  behavior introduced in 9b2c0d0b.
2026-03-03 14:54:17 -08:00
ChanningHe
94353aea44 feat(integration): add domain CRUD endpoints to integration API 2026-03-03 14:54:17 -08:00
Owen Schwartz
cdf79edb00 Merge pull request #2570 from Fizza-Mukhtar/fix/mixed-target-failover-2448
fix: local targets ignored when newt site is unhealthy (mixed target failover)
2026-03-01 15:58:25 -08:00
Owen Schwartz
44e3eedffa Merge pull request #2567 from marcschaeferger/fix-kubernetes-install
feat(kubernetes): enable newtInstances by default and update installation instructions
2026-03-01 10:56:18 -08:00
Marc Schäfer
bb189874cb fix(newt-install): conditionally display Kubernetes installation info
Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-03-01 10:55:58 -08:00
Marc Schäfer
34dadd0e16 feat(kubernetes): enable newtInstances by default and update installation instructions
Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-03-01 10:55:58 -08:00
Owen Schwartz
87b5cd9988 Merge pull request #2573 from Fizza-Mukhtar/fix/container-search-excludes-labels-2228
fix: exclude labels from container search to prevent false positives
2026-03-01 10:52:50 -08:00
Marc Schäfer
6a537a23e8 fix(newt-install): conditionally display Kubernetes installation info
Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-03-01 18:17:45 +01:00
Fizza-Mukhtar
e63a6e9b77 fix: treat local and wireguard sites as online for failover 2026-03-01 07:56:47 -08:00
Fizza-Mukhtar
7ce589c4f2 fix: exclude labels from container search to prevent false positives 2026-03-01 06:50:03 -08:00
Fizza-Mukhtar
f36cf06e26 fix: fallback to local targets when newt targets are unhealthy 2026-03-01 01:43:15 -08:00
Marc Schäfer
375211f184 feat(kubernetes): enable newtInstances by default and update installation instructions
Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-02-28 23:56:28 +01:00
Owen
66c377a5c9 Merge branch 'main' into dev 2026-02-28 12:14:41 -08:00
Owen
50c2aa0111 Add default memory limits 2026-02-28 12:14:27 -08:00
Owen
fdeb891137 Fix pagination effecting drop downs 2026-02-28 12:07:42 -08:00
Owen Schwartz
6a6e3a43b1 Merge pull request #2562 from LaurenceJJones/fix/zod-openapi-catch-error
fix(zod): Add openapi call after catch
2026-02-28 11:04:10 -08:00
Laurence
b0a34fa21b fix(openapi): Add openapi call after catch
fix: #2561
without making an explicit call to openapi a runtime error happens because it cannot infer the type, the call to openapi is the same across the codebase
2026-02-28 11:27:19 +00:00
Owen
72bf6f3c41 Comma seperated 2026-02-27 17:53:44 -08:00
miloschwartz
ad9289e0c1 sort by name by default 2026-02-27 15:53:27 -08:00
Owen Schwartz
b0cb0e5a99 Merge pull request #2559 from fosrl/dev
1.16.1
2026-02-27 12:40:23 -08:00
miloschwartz
8347203bbe add sort to name col 2026-02-27 12:39:26 -08:00
miloschwartz
4aa1186aed fix machine client pagination 2026-02-27 11:59:55 -08:00
Owen
eed87af61d Use ecr base to build 2026-02-26 21:43:14 -08:00
Owen
daeea8e7ea Add alises to quieries
Fixes #2556
2026-02-26 21:37:47 -08:00
Owen
0d63a15715 Merge branch 'main' into dev 2026-02-26 20:14:41 -08:00
miloschwartz
fa2e229ada support authPath in device login 2026-02-26 14:59:34 -08:00
125 changed files with 925 additions and 442 deletions

View File

@@ -1,4 +1,5 @@
FROM node:24-slim AS base # FROM node:24-slim AS base
FROM public.ecr.aws/docker/library/node:24-slim AS base
WORKDIR /app WORKDIR /app
@@ -31,7 +32,8 @@ FROM base AS builder
RUN npm ci --omit=dev RUN npm ci --omit=dev
FROM node:24-slim AS runner # FROM node:24-slim AS runner
FROM public.ecr.aws/docker/library/node:24-slim AS runner
WORKDIR /app WORKDIR /app

View File

@@ -4,6 +4,12 @@ services:
image: fosrl/pangolin:latest image: fosrl/pangolin:latest
container_name: pangolin container_name: pangolin
restart: unless-stopped restart: unless-stopped
deploy:
resources:
limits:
memory: 1g
reservations:
memory: 256m
volumes: volumes:
- ./config:/app/config - ./config:/app/config
healthcheck: healthcheck:

View File

@@ -4,6 +4,12 @@ services:
image: docker.io/fosrl/pangolin:{{if .IsEnterprise}}ee-{{end}}{{.PangolinVersion}} image: docker.io/fosrl/pangolin:{{if .IsEnterprise}}ee-{{end}}{{.PangolinVersion}}
container_name: pangolin container_name: pangolin
restart: unless-stopped restart: unless-stopped
deploy:
resources:
limits:
memory: 1g
reservations:
memory: 256m
volumes: volumes:
- ./config:/app/config - ./config:/app/config
healthcheck: healthcheck:

View File

@@ -1102,6 +1102,12 @@
"actionGetUser": "Get User", "actionGetUser": "Get User",
"actionGetOrgUser": "Get Organization User", "actionGetOrgUser": "Get Organization User",
"actionListOrgDomains": "List Organization Domains", "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", "actionCreateSite": "Create Site",
"actionDeleteSite": "Delete Site", "actionDeleteSite": "Delete Site",
"actionGetSite": "Get Site", "actionGetSite": "Get Site",
@@ -1670,10 +1676,10 @@
"sshSudoModeCommandsDescription": "User can run only the specified commands with sudo.", "sshSudoModeCommandsDescription": "User can run only the specified commands with sudo.",
"sshSudo": "Allow sudo", "sshSudo": "Allow sudo",
"sshSudoCommands": "Sudo Commands", "sshSudoCommands": "Sudo Commands",
"sshSudoCommandsDescription": "List of commands the user is allowed to run with sudo.", "sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo.",
"sshCreateHomeDir": "Create Home Directory", "sshCreateHomeDir": "Create Home Directory",
"sshUnixGroups": "Unix Groups", "sshUnixGroups": "Unix Groups",
"sshUnixGroupsDescription": "Unix groups to add the user to on the target host.", "sshUnixGroupsDescription": "Comma separated Unix groups to add the user to on the target host.",
"retryAttempts": "Retry Attempts", "retryAttempts": "Retry Attempts",
"expectedResponseCodes": "Expected Response Codes", "expectedResponseCodes": "Expected Response Codes",
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.", "expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",

View File

@@ -17,6 +17,7 @@ import fs from "fs";
import path from "path"; import path from "path";
import { APP_PATH } from "./lib/consts"; import { APP_PATH } from "./lib/consts";
import yaml from "js-yaml"; import yaml from "js-yaml";
import { z } from "zod";
const dev = process.env.ENVIRONMENT !== "prod"; const dev = process.env.ENVIRONMENT !== "prod";
const externalPort = config.getRawConfig().server.integration_port; const externalPort = config.getRawConfig().server.integration_port;
@@ -38,12 +39,24 @@ export function createIntegrationApiServer() {
apiServer.use(cookieParser()); apiServer.use(cookieParser());
apiServer.use(express.json()); apiServer.use(express.json());
const openApiDocumentation = getOpenApiDocumentation();
apiServer.use( apiServer.use(
"/v1/docs", "/v1/docs",
swaggerUi.serve, 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 // API routes
const prefix = `/v1`; const prefix = `/v1`;
apiServer.use(logIncomingMiddleware); 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({ registry.registerPath({
method: "get", method: "get",
path: "/", path: "/",
@@ -94,6 +97,74 @@ function getOpenApiDocumentation() {
responses: {} 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 generator = new OpenApiGeneratorV3(registry.definitions);
const generated = generator.generateDocument({ const generated = generator.generateDocument({

View File

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

View File

@@ -14,3 +14,4 @@ export * from "./verifyApiKeyApiKeyAccess";
export * from "./verifyApiKeyClientAccess"; export * from "./verifyApiKeyClientAccess";
export * from "./verifyApiKeySiteResourceAccess"; export * from "./verifyApiKeySiteResourceAccess";
export * from "./verifyApiKeyIdpAccess"; export * from "./verifyApiKeyIdpAccess";
export * from "./verifyApiKeyDomainAccess";

View 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"
)
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ registry.registerPath({
path: "/org/{orgId}/user/{userId}/client", path: "/org/{orgId}/user/{userId}/client",
description: description:
"Create a new client for a user and associate it with an existing olm.", "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: { request: {
params: paramsSchema, params: paramsSchema,
body: { body: {

View File

@@ -243,7 +243,7 @@ registry.registerPath({
path: "/org/{orgId}/client/{niceId}", path: "/org/{orgId}/client/{niceId}",
description: description:
"Get a client by orgId and niceId. NiceId is a readable ID for the site and unique on a per org basis.", "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: { request: {
params: z.object({ params: z.object({
orgId: z.string(), orgId: z.string(),

View File

@@ -119,12 +119,12 @@ const listClientsSchema = z.object({
}), }),
query: z.string().optional(), query: z.string().optional(),
sort_by: z sort_by: z
.enum(["megabytesIn", "megabytesOut"]) .enum(["name", "megabytesIn", "megabytesOut"])
.optional() .optional()
.catch(undefined) .catch(undefined)
.openapi({ .openapi({
type: "string", type: "string",
enum: ["megabytesIn", "megabytesOut"], enum: ["name", "megabytesIn", "megabytesOut"],
description: "Field to sort by" description: "Field to sort by"
}), }),
order: z order: z
@@ -237,7 +237,7 @@ registry.registerPath({
method: "get", method: "get",
path: "/org/{orgId}/clients", path: "/org/{orgId}/clients",
description: "List all clients for an organization.", description: "List all clients for an organization.",
tags: [OpenAPITags.Client, OpenAPITags.Org], tags: [OpenAPITags.Client],
request: { request: {
query: listClientsSchema, query: listClientsSchema,
params: listClientsParamsSchema params: listClientsParamsSchema
@@ -363,14 +363,14 @@ export async function listClients(
const countQuery = db.$count(baseQuery.as("filtered_clients")); const countQuery = db.$count(baseQuery.as("filtered_clients"));
const listMachinesQuery = baseQuery const listMachinesQuery = baseQuery
.limit(page) .limit(pageSize)
.offset(pageSize * (page - 1)) .offset(pageSize * (page - 1))
.orderBy( .orderBy(
sort_by sort_by
? order === "asc" ? order === "asc"
? asc(clients[sort_by]) ? asc(clients[sort_by])
: desc(clients[sort_by]) : desc(clients[sort_by])
: asc(clients.clientId) : asc(clients.name)
); );
const [clientsList, totalCount] = await Promise.all([ const [clientsList, totalCount] = await Promise.all([

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,7 +27,8 @@ import {
verifyApiKeyClientAccess, verifyApiKeyClientAccess,
verifyApiKeySiteResourceAccess, verifyApiKeySiteResourceAccess,
verifyApiKeySetResourceClients, verifyApiKeySetResourceClients,
verifyLimits verifyLimits,
verifyApiKeyDomainAccess
} from "@server/middlewares"; } from "@server/middlewares";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { Router } from "express"; import { Router } from "express";
@@ -347,6 +348,56 @@ authenticated.get(
domain.listDomains 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( authenticated.get(
"/org/:orgId/invitations", "/org/:orgId/invitations",
verifyApiKeyOrgAccess, verifyApiKeyOrgAccess,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,7 @@ import {
and, and,
asc, asc,
count, count,
desc,
eq, eq,
inArray, inArray,
isNull, isNull,
@@ -63,6 +64,26 @@ const listResourcesSchema = z.object({
description: "Page number to retrieve" description: "Page number to retrieve"
}), }),
query: z.string().optional(), query: z.string().optional(),
sort_by: z
.enum(["name"])
.optional()
.catch(undefined)
.openapi({
type: "string",
enum: ["name"],
description: "Field to sort by"
}),
order: z
.enum(["asc", "desc"])
.optional()
.default("asc")
.catch("asc")
.openapi({
type: "string",
enum: ["asc", "desc"],
default: "asc",
description: "Sort order"
}),
enabled: z enabled: z
.enum(["true", "false"]) .enum(["true", "false"])
.transform((v) => v === "true") .transform((v) => v === "true")
@@ -204,7 +225,7 @@ registry.registerPath({
method: "get", method: "get",
path: "/org/{orgId}/resources", path: "/org/{orgId}/resources",
description: "List resources for an organization.", description: "List resources for an organization.",
tags: [OpenAPITags.Org, OpenAPITags.Resource], tags: [OpenAPITags.PublicResource],
request: { request: {
params: z.object({ params: z.object({
orgId: z.string() orgId: z.string()
@@ -229,8 +250,16 @@ export async function listResources(
) )
); );
} }
const { page, pageSize, authState, enabled, query, healthStatus } = const {
parsedQuery.data; page,
pageSize,
authState,
enabled,
query,
healthStatus,
sort_by,
order
} = parsedQuery.data;
const parsedParams = listResourcesParamsSchema.safeParse(req.params); const parsedParams = listResourcesParamsSchema.safeParse(req.params);
if (!parsedParams.success) { if (!parsedParams.success) {
@@ -395,7 +424,13 @@ export async function listResources(
baseQuery baseQuery
.limit(pageSize) .limit(pageSize)
.offset(pageSize * (page - 1)) .offset(pageSize * (page - 1))
.orderBy(asc(resources.resourceId)), .orderBy(
sort_by
? order === "asc"
? asc(resources[sort_by])
: desc(resources[sort_by])
: asc(resources.name)
),
countQuery countQuery
]); ]);

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,7 @@ registry.registerPath({
path: "/resource/{resourceId}/header-auth", path: "/resource/{resourceId}/header-auth",
description: description:
"Set or update the header authentication for a resource. If user and password is not provided, it will remove the header authentication.", "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: { request: {
params: setResourceAuthMethodsParamsSchema, params: setResourceAuthMethodsParamsSchema,
body: { body: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -51,7 +51,7 @@ registry.registerPath({
path: "/org/{orgId}/site/{niceId}", path: "/org/{orgId}/site/{niceId}",
description: description:
"Get a site by orgId and niceId. NiceId is a readable ID for the site and unique on a per org basis.", "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: { request: {
params: z.object({ params: z.object({
orgId: z.string(), orgId: z.string(),

View File

@@ -108,12 +108,12 @@ const listSitesSchema = z.object({
}), }),
query: z.string().optional(), query: z.string().optional(),
sort_by: z sort_by: z
.enum(["megabytesIn", "megabytesOut"]) .enum(["name", "megabytesIn", "megabytesOut"])
.optional() .optional()
.catch(undefined) .catch(undefined)
.openapi({ .openapi({
type: "string", type: "string",
enum: ["megabytesIn", "megabytesOut"], enum: ["name", "megabytesIn", "megabytesOut"],
description: "Field to sort by" description: "Field to sort by"
}), }),
order: z order: z
@@ -180,7 +180,7 @@ registry.registerPath({
method: "get", method: "get",
path: "/org/{orgId}/sites", path: "/org/{orgId}/sites",
description: "List all sites in an organization", description: "List all sites in an organization",
tags: [OpenAPITags.Org, OpenAPITags.Site], tags: [OpenAPITags.Site],
request: { request: {
params: listSitesParamsSchema, params: listSitesParamsSchema,
query: listSitesSchema query: listSitesSchema
@@ -278,7 +278,7 @@ export async function listSites(
// we need to add `as` so that drizzle filters the result as a subquery // we need to add `as` so that drizzle filters the result as a subquery
const countQuery = db.$count( const countQuery = db.$count(
querySitesBase().where(and(...conditions)) querySitesBase().where(and(...conditions)).as("filtered_sites")
); );
const siteListQuery = baseQuery const siteListQuery = baseQuery
@@ -289,7 +289,7 @@ export async function listSites(
? order === "asc" ? order === "asc"
? asc(sites[sort_by]) ? asc(sites[sort_by])
: desc(sites[sort_by]) : desc(sites[sort_by])
: asc(sites.siteId) : asc(sites.name)
); );
const [totalCount, rows] = await Promise.all([ const [totalCount, rows] = await Promise.all([

View File

@@ -35,7 +35,7 @@ registry.registerPath({
path: "/org/{orgId}/pick-site-defaults", path: "/org/{orgId}/pick-site-defaults",
description: description:
"Return pre-requisite data for creating a site, such as the exit node, subnet, Newt credentials, etc.", "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: { request: {
params: z.object({ params: z.object({
orgId: z.string() orgId: z.string()

View File

@@ -30,7 +30,7 @@ registry.registerPath({
path: "/site-resource/{siteResourceId}/clients/add", path: "/site-resource/{siteResourceId}/clients/add",
description: description:
"Add a single client to a site resource. Clients with a userId cannot be added.", "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: { request: {
params: addClientToSiteResourceParamsSchema, params: addClientToSiteResourceParamsSchema,
body: { body: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import type { PaginatedResponse } from "@server/types/Pagination"; import type { PaginatedResponse } from "@server/types/Pagination";
import { and, asc, eq, like, or, sql } from "drizzle-orm"; import { and, asc, desc, eq, like, or, sql } from "drizzle-orm";
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import { z } from "zod"; import { z } from "zod";
@@ -48,6 +48,26 @@ const listAllSiteResourcesByOrgQuerySchema = z.object({
type: "string", type: "string",
enum: ["host", "cidr"], enum: ["host", "cidr"],
description: "Filter site resources by mode" description: "Filter site resources by mode"
}),
sort_by: z
.enum(["name"])
.optional()
.catch(undefined)
.openapi({
type: "string",
enum: ["name"],
description: "Field to sort by"
}),
order: z
.enum(["asc", "desc"])
.optional()
.default("asc")
.catch("asc")
.openapi({
type: "string",
enum: ["asc", "desc"],
default: "asc",
description: "Sort order"
}) })
}); });
@@ -92,7 +112,7 @@ registry.registerPath({
method: "get", method: "get",
path: "/org/{orgId}/site-resources", path: "/org/{orgId}/site-resources",
description: "List all site resources for an organization.", description: "List all site resources for an organization.",
tags: [OpenAPITags.Client, OpenAPITags.Org], tags: [OpenAPITags.PrivateResource],
request: { request: {
params: listAllSiteResourcesByOrgParamsSchema, params: listAllSiteResourcesByOrgParamsSchema,
query: listAllSiteResourcesByOrgQuerySchema query: listAllSiteResourcesByOrgQuerySchema
@@ -131,7 +151,8 @@ export async function listAllSiteResourcesByOrg(
} }
const { orgId } = parsedParams.data; const { orgId } = parsedParams.data;
const { page, pageSize, query, mode } = parsedQuery.data; const { page, pageSize, query, mode, sort_by, order } =
parsedQuery.data;
const conditions = [and(eq(siteResources.orgId, orgId))]; const conditions = [and(eq(siteResources.orgId, orgId))];
if (query) { if (query) {
@@ -172,14 +193,20 @@ export async function listAllSiteResourcesByOrg(
const baseQuery = querySiteResourcesBase().where(and(...conditions)); const baseQuery = querySiteResourcesBase().where(and(...conditions));
const countQuery = db.$count( const countQuery = db.$count(
querySiteResourcesBase().where(and(...conditions)) querySiteResourcesBase().where(and(...conditions)).as("filtered_site_resources")
); );
const [siteResourcesList, totalCount] = await Promise.all([ const [siteResourcesList, totalCount] = await Promise.all([
baseQuery baseQuery
.limit(pageSize) .limit(pageSize)
.offset(pageSize * (page - 1)) .offset(pageSize * (page - 1))
.orderBy(asc(siteResources.siteResourceId)), .orderBy(
sort_by
? order === "asc"
? asc(siteResources[sort_by])
: desc(siteResources[sort_by])
: asc(siteResources.name)
),
countQuery countQuery
]); ]);

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import { siteResources, sites, SiteResource } from "@server/db";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import { eq, and } from "drizzle-orm"; import { and, asc, desc, eq } from "drizzle-orm";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import logger from "@server/logger"; import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
@@ -27,7 +27,27 @@ const listSiteResourcesQuerySchema = z.object({
.optional() .optional()
.default("0") .default("0")
.transform(Number) .transform(Number)
.pipe(z.int().nonnegative()) .pipe(z.int().nonnegative()),
sort_by: z
.enum(["name"])
.optional()
.catch(undefined)
.openapi({
type: "string",
enum: ["name"],
description: "Field to sort by"
}),
order: z
.enum(["asc", "desc"])
.optional()
.default("asc")
.catch("asc")
.openapi({
type: "string",
enum: ["asc", "desc"],
default: "asc",
description: "Sort order"
})
}); });
export type ListSiteResourcesResponse = { export type ListSiteResourcesResponse = {
@@ -38,7 +58,7 @@ registry.registerPath({
method: "get", method: "get",
path: "/org/{orgId}/site/{siteId}/resources", path: "/org/{orgId}/site/{siteId}/resources",
description: "List site resources for a site.", description: "List site resources for a site.",
tags: [OpenAPITags.Client, OpenAPITags.Org], tags: [OpenAPITags.PrivateResource],
request: { request: {
params: listSiteResourcesParamsSchema, params: listSiteResourcesParamsSchema,
query: listSiteResourcesQuerySchema query: listSiteResourcesQuerySchema
@@ -75,7 +95,7 @@ export async function listSiteResources(
} }
const { siteId, orgId } = parsedParams.data; const { siteId, orgId } = parsedParams.data;
const { limit, offset } = parsedQuery.data; const { limit, offset, sort_by, order } = parsedQuery.data;
// Verify the site exists and belongs to the org // Verify the site exists and belongs to the org
const site = await db const site = await db
@@ -98,6 +118,13 @@ export async function listSiteResources(
eq(siteResources.orgId, orgId) eq(siteResources.orgId, orgId)
) )
) )
.orderBy(
sort_by
? order === "asc"
? asc(siteResources[sort_by])
: desc(siteResources[sort_by])
: asc(siteResources.name)
)
.limit(limit) .limit(limit)
.offset(offset); .offset(offset);

View File

@@ -30,7 +30,7 @@ registry.registerPath({
path: "/site-resource/{siteResourceId}/clients/remove", path: "/site-resource/{siteResourceId}/clients/remove",
description: description:
"Remove a single client from a site resource. Clients with a userId cannot be removed.", "Remove a single client from a site resource. Clients with a userId cannot be removed.",
tags: [OpenAPITags.Resource, OpenAPITags.Client], tags: [OpenAPITags.PrivateResource, OpenAPITags.Client],
request: { request: {
params: removeClientFromSiteResourceParamsSchema, params: removeClientFromSiteResourceParamsSchema,
body: { body: {

View File

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

View File

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

View File

@@ -30,7 +30,7 @@ registry.registerPath({
path: "/site-resource/{siteResourceId}/clients", path: "/site-resource/{siteResourceId}/clients",
description: description:
"Set clients for a site resource. This will replace all existing clients. Clients with a userId cannot be added.", "Set clients for a site resource. This will replace all existing clients. Clients with a userId cannot be added.",
tags: [OpenAPITags.Resource, OpenAPITags.Client], tags: [OpenAPITags.PrivateResource, OpenAPITags.Client],
request: { request: {
params: setSiteResourceClientsParamsSchema, params: setSiteResourceClientsParamsSchema,
body: { body: {

View File

@@ -31,7 +31,7 @@ registry.registerPath({
path: "/site-resource/{siteResourceId}/roles", path: "/site-resource/{siteResourceId}/roles",
description: description:
"Set roles for a site resource. This will replace all existing roles.", "Set roles for a site resource. This will replace all existing roles.",
tags: [OpenAPITags.Resource, OpenAPITags.Role], tags: [OpenAPITags.PrivateResource, OpenAPITags.Role],
request: { request: {
params: setSiteResourceRolesParamsSchema, params: setSiteResourceRolesParamsSchema,
body: { body: {

View File

@@ -31,7 +31,7 @@ registry.registerPath({
path: "/site-resource/{siteResourceId}/users", path: "/site-resource/{siteResourceId}/users",
description: description:
"Set users for a site resource. This will replace all existing users.", "Set users for a site resource. This will replace all existing users.",
tags: [OpenAPITags.Resource, OpenAPITags.User], tags: [OpenAPITags.PrivateResource, OpenAPITags.User],
request: { request: {
params: setSiteResourceUsersParamsSchema, params: setSiteResourceUsersParamsSchema,
body: { body: {

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