Merge branch 'dev' into clients-user

This commit is contained in:
miloschwartz
2025-12-04 11:38:05 -05:00
31 changed files with 3098 additions and 313 deletions

View File

@@ -13,9 +13,12 @@ function createDb() {
connection_string: process.env.POSTGRES_CONNECTION_STRING
};
if (process.env.POSTGRES_REPLICA_CONNECTION_STRINGS) {
const replicas = process.env.POSTGRES_REPLICA_CONNECTION_STRINGS.split(",").map((conn) => ({
connection_string: conn.trim()
}));
const replicas =
process.env.POSTGRES_REPLICA_CONNECTION_STRINGS.split(
","
).map((conn) => ({
connection_string: conn.trim()
}));
config.postgres.replicas = replicas;
}
} else {
@@ -40,28 +43,44 @@ function createDb() {
connectionString,
max: poolConfig?.max_connections || 20,
idleTimeoutMillis: poolConfig?.idle_timeout_ms || 30000,
connectionTimeoutMillis: poolConfig?.connection_timeout_ms || 5000,
connectionTimeoutMillis: poolConfig?.connection_timeout_ms || 5000
});
const replicas = [];
if (!replicaConnections.length) {
replicas.push(DrizzlePostgres(primaryPool));
replicas.push(
DrizzlePostgres(primaryPool, {
logger: process.env.NODE_ENV === "development"
})
);
} else {
for (const conn of replicaConnections) {
const replicaPool = new Pool({
connectionString: conn.connection_string,
max: poolConfig?.max_replica_connections || 20,
idleTimeoutMillis: poolConfig?.idle_timeout_ms || 30000,
connectionTimeoutMillis: poolConfig?.connection_timeout_ms || 5000,
connectionTimeoutMillis:
poolConfig?.connection_timeout_ms || 5000
});
replicas.push(DrizzlePostgres(replicaPool));
replicas.push(
DrizzlePostgres(replicaPool, {
logger: process.env.NODE_ENV === "development"
})
);
}
}
return withReplicas(DrizzlePostgres(primaryPool), replicas as any);
return withReplicas(
DrizzlePostgres(primaryPool, {
logger: process.env.NODE_ENV === "development"
}),
replicas as any
);
}
export const db = createDb();
export default db;
export type Transaction = Parameters<Parameters<typeof db["transaction"]>[0]>[0];
export type Transaction = Parameters<
Parameters<(typeof db)["transaction"]>[0]
>[0];

View File

@@ -6,7 +6,11 @@ import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { queryAccessAuditLogsQuery, queryRequestAuditLogsParams, queryRequest } from "./queryRequstAuditLog";
import {
queryAccessAuditLogsQuery,
queryRequestAuditLogsParams,
queryRequest
} from "./queryRequestAuditLog";
import { generateCSV } from "./generateCSV";
registry.registerPath({
@@ -54,10 +58,13 @@ export async function exportRequestAuditLogs(
const log = await baseQuery.limit(data.limit).offset(data.offset);
const csvData = generateCSV(log);
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', `attachment; filename="request-audit-logs-${data.orgId}-${Date.now()}.csv"`);
res.setHeader("Content-Type", "text/csv");
res.setHeader(
"Content-Disposition",
`attachment; filename="request-audit-logs-${data.orgId}-${Date.now()}.csv"`
);
return res.send(csvData);
} catch (error) {
logger.error(error);

View File

@@ -1,2 +1,3 @@
export * from "./queryRequstAuditLog";
export * from "./exportRequstAuditLog";
export * from "./queryRequestAuditLog";
export * from "./queryRequestAnalytics";
export * from "./exportRequestAuditLog";

View File

@@ -0,0 +1,192 @@
import { db, requestAuditLog, driver } from "@server/db";
import { registry } from "@server/openApi";
import { NextFunction } from "express";
import { Request, Response } from "express";
import { eq, gt, lt, and, count, sql, desc, not, isNull } from "drizzle-orm";
import { OpenAPITags } from "@server/openApi";
import { z } from "zod";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import response from "@server/lib/response";
import logger from "@server/logger";
const queryAccessAuditLogsQuery = z.object({
// iso string just validate its a parseable date
timeStart: z
.string()
.refine((val) => !isNaN(Date.parse(val)), {
error: "timeStart must be a valid ISO date string"
})
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional(),
timeEnd: z
.string()
.refine((val) => !isNaN(Date.parse(val)), {
error: "timeEnd must be a valid ISO date string"
})
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional()
.prefault(new Date().toISOString())
.openapi({
type: "string",
format: "date-time",
description:
"End time as ISO date string (defaults to current time)"
}),
resourceId: z
.string()
.optional()
.transform(Number)
.pipe(z.int().positive())
.optional()
});
const queryRequestAuditLogsParams = z.object({
orgId: z.string()
});
const queryRequestAuditLogsCombined = queryAccessAuditLogsQuery.merge(
queryRequestAuditLogsParams
);
type Q = z.infer<typeof queryRequestAuditLogsCombined>;
async function query(query: Q) {
let baseConditions = and(
eq(requestAuditLog.orgId, query.orgId),
lt(requestAuditLog.timestamp, query.timeEnd)
);
if (query.timeStart) {
baseConditions = and(
baseConditions,
gt(requestAuditLog.timestamp, query.timeStart)
);
}
if (query.resourceId) {
baseConditions = and(
baseConditions,
eq(requestAuditLog.resourceId, query.resourceId)
);
}
const [all] = await db
.select({ total: count() })
.from(requestAuditLog)
.where(baseConditions);
const [blocked] = await db
.select({ total: count() })
.from(requestAuditLog)
.where(and(baseConditions, eq(requestAuditLog.action, false)));
const totalQ = sql<number>`count(${requestAuditLog.id})`
.mapWith(Number)
.as("total");
const requestsPerCountry = await db
.selectDistinct({
code: requestAuditLog.location,
count: totalQ
})
.from(requestAuditLog)
.where(and(baseConditions, not(isNull(requestAuditLog.location))))
.groupBy(requestAuditLog.location)
.orderBy(desc(totalQ));
const groupByDayFunction =
driver === "pg"
? sql<string>`DATE_TRUNC('day', TO_TIMESTAMP(${requestAuditLog.timestamp}))`
: sql<string>`DATE(${requestAuditLog.timestamp}, 'unixepoch')`;
const booleanTrue = driver === "pg" ? sql`true` : sql`1`;
const booleanFalse = driver === "pg" ? sql`false` : sql`0`;
const requestsPerDay = await db
.select({
day: groupByDayFunction.as("day"),
allowedCount:
sql<number>`SUM(CASE WHEN ${requestAuditLog.action} = ${booleanTrue} THEN 1 ELSE 0 END)`.as(
"allowed_count"
),
blockedCount:
sql<number>`SUM(CASE WHEN ${requestAuditLog.action} = ${booleanFalse} THEN 1 ELSE 0 END)`.as(
"blocked_count"
),
totalCount: sql<number>`COUNT(*)`.as("total_count")
})
.from(requestAuditLog)
.where(and(baseConditions))
.groupBy(groupByDayFunction)
.orderBy(groupByDayFunction);
return {
requestsPerCountry: requestsPerCountry as Array<{
code: string;
count: number;
}>,
requestsPerDay,
totalBlocked: blocked.total,
totalRequests: all.total
};
}
registry.registerPath({
method: "get",
path: "/org/{orgId}/logs/analytics",
description: "Query the request audit analytics for an organization",
tags: [OpenAPITags.Org],
request: {
query: queryAccessAuditLogsQuery,
params: queryRequestAuditLogsParams
},
responses: {}
});
export type QueryRequestAnalyticsResponse = Awaited<ReturnType<typeof query>>;
export async function queryRequestAnalytics(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = queryAccessAuditLogsQuery.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedQuery.error)
)
);
}
const parsedParams = queryRequestAuditLogsParams.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error)
)
);
}
const params = { ...parsedQuery.data, ...parsedParams.data };
const data = await query(params);
return response<QueryRequestAnalyticsResponse>(res, {
data,
success: true,
error: false,
message: "Request audit analytics retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -31,7 +31,8 @@ export const queryAccessAuditLogsQuery = z.object({
.openapi({
type: "string",
format: "date-time",
description: "End time as ISO date string (defaults to current time)"
description:
"End time as ISO date string (defaults to current time)"
}),
action: z
.union([z.boolean(), z.string()])
@@ -72,8 +73,9 @@ export const queryRequestAuditLogsParams = z.object({
orgId: z.string()
});
export const queryRequestAuditLogsCombined =
queryAccessAuditLogsQuery.merge(queryRequestAuditLogsParams);
export const queryRequestAuditLogsCombined = queryAccessAuditLogsQuery.merge(
queryRequestAuditLogsParams
);
type Q = z.infer<typeof queryRequestAuditLogsCombined>;
function getWhere(data: Q) {
@@ -209,11 +211,21 @@ async function queryUniqueFilterAttributes(
.where(baseConditions);
return {
actors: uniqueActors.map(row => row.actor).filter((actor): actor is string => actor !== null),
resources: uniqueResources.filter((row): row is { id: number; name: string | null } => row.id !== null),
locations: uniqueLocations.map(row => row.locations).filter((location): location is string => location !== null),
hosts: uniqueHosts.map(row => row.hosts).filter((host): host is string => host !== null),
paths: uniquePaths.map(row => row.paths).filter((path): path is string => path !== null)
actors: uniqueActors
.map((row) => row.actor)
.filter((actor): actor is string => actor !== null),
resources: uniqueResources.filter(
(row): row is { id: number; name: string | null } => row.id !== null
),
locations: uniqueLocations
.map((row) => row.locations)
.filter((location): location is string => location !== null),
hosts: uniqueHosts
.map((row) => row.hosts)
.filter((host): host is string => host !== null),
paths: uniquePaths
.map((row) => row.paths)
.filter((path): path is string => path !== null)
};
}
@@ -270,7 +282,7 @@ export async function queryRequestAuditLogs(
},
success: true,
error: false,
message: "Action audit logs retrieved successfully",
message: "Request audit logs retrieved successfully",
status: HttpCode.OK
});
} catch (error) {

View File

@@ -81,7 +81,7 @@ authenticated.post(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.updateOrg),
logActionAudit(ActionsEnum.updateOrg),
org.updateOrg,
org.updateOrg
);
if (build !== "saas") {
@@ -91,7 +91,7 @@ if (build !== "saas") {
verifyUserIsOrgOwner,
verifyUserHasAction(ActionsEnum.deleteOrg),
logActionAudit(ActionsEnum.deleteOrg),
org.deleteOrg,
org.deleteOrg
);
}
@@ -154,7 +154,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createClient),
logActionAudit(ActionsEnum.createClient),
client.createClient,
client.createClient
);
// TODO: Separate into a deleteUserClient (for user clients) and deleteClient (for machine clients)
@@ -163,7 +163,7 @@ authenticated.delete(
verifyClientAccess,
verifyUserHasAction(ActionsEnum.deleteClient),
logActionAudit(ActionsEnum.deleteClient),
client.deleteClient,
client.deleteClient
);
authenticated.post(
@@ -171,10 +171,9 @@ authenticated.post(
verifyClientAccess, // this will check if the user has access to the client
verifyUserHasAction(ActionsEnum.updateClient), // this will check if the user has permission to update the client
logActionAudit(ActionsEnum.updateClient),
client.updateClient,
client.updateClient
);
// authenticated.get(
// "/site/:siteId/roles",
// verifySiteAccess,
@@ -186,7 +185,7 @@ authenticated.post(
verifySiteAccess,
verifyUserHasAction(ActionsEnum.updateSite),
logActionAudit(ActionsEnum.updateSite),
site.updateSite,
site.updateSite
);
authenticated.delete(
@@ -194,7 +193,7 @@ authenticated.delete(
verifySiteAccess,
verifyUserHasAction(ActionsEnum.deleteSite),
logActionAudit(ActionsEnum.deleteSite),
site.deleteSite,
site.deleteSite
);
// TODO: BREAK OUT THESE ACTIONS SO THEY ARE NOT ALL "getSite"
@@ -214,13 +213,13 @@ authenticated.post(
"/site/:siteId/docker/check",
verifySiteAccess,
verifyUserHasAction(ActionsEnum.getSite),
site.checkDockerSocket,
site.checkDockerSocket
);
authenticated.post(
"/site/:siteId/docker/trigger",
verifySiteAccess,
verifyUserHasAction(ActionsEnum.getSite),
site.triggerFetchContainers,
site.triggerFetchContainers
);
authenticated.get(
"/site/:siteId/docker/containers",
@@ -236,7 +235,7 @@ authenticated.put(
verifySiteAccess,
verifyUserHasAction(ActionsEnum.createSiteResource),
logActionAudit(ActionsEnum.createSiteResource),
siteResource.createSiteResource,
siteResource.createSiteResource
);
authenticated.get(
@@ -270,7 +269,7 @@ authenticated.post(
verifySiteResourceAccess,
verifyUserHasAction(ActionsEnum.updateSiteResource),
logActionAudit(ActionsEnum.updateSiteResource),
siteResource.updateSiteResource,
siteResource.updateSiteResource
);
authenticated.delete(
@@ -280,7 +279,7 @@ authenticated.delete(
verifySiteResourceAccess,
verifyUserHasAction(ActionsEnum.deleteSiteResource),
logActionAudit(ActionsEnum.deleteSiteResource),
siteResource.deleteSiteResource,
siteResource.deleteSiteResource
);
authenticated.get(
@@ -354,7 +353,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createResource),
logActionAudit(ActionsEnum.createResource),
resource.createResource,
resource.createResource
);
authenticated.get(
@@ -370,6 +369,13 @@ authenticated.get(
resource.listResources
);
authenticated.get(
"/org/:orgId/resource-names",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.listResources),
resource.listAllResourceNames
);
authenticated.get(
"/org/:orgId/user-resources",
verifyOrgAccess,
@@ -416,7 +422,7 @@ authenticated.delete(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.removeInvitation),
logActionAudit(ActionsEnum.removeInvitation),
user.removeInvitation,
user.removeInvitation
);
authenticated.post(
@@ -424,7 +430,7 @@ authenticated.post(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.inviteUser),
logActionAudit(ActionsEnum.inviteUser),
user.inviteUser,
user.inviteUser
); // maybe make this /invite/create instead
unauthenticated.post("/invite/accept", user.acceptInvite); // this is supposed to be unauthenticated
@@ -460,14 +466,14 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.updateResource),
logActionAudit(ActionsEnum.updateResource),
resource.updateResource,
resource.updateResource
);
authenticated.delete(
"/resource/:resourceId",
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.deleteResource),
logActionAudit(ActionsEnum.deleteResource),
resource.deleteResource,
resource.deleteResource
);
authenticated.put(
@@ -475,7 +481,7 @@ authenticated.put(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.createTarget),
logActionAudit(ActionsEnum.createTarget),
target.createTarget,
target.createTarget
);
authenticated.get(
"/resource/:resourceId/targets",
@@ -489,7 +495,7 @@ authenticated.put(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.createResourceRule),
logActionAudit(ActionsEnum.createResourceRule),
resource.createResourceRule,
resource.createResourceRule
);
authenticated.get(
"/resource/:resourceId/rules",
@@ -502,14 +508,14 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.updateResourceRule),
logActionAudit(ActionsEnum.updateResourceRule),
resource.updateResourceRule,
resource.updateResourceRule
);
authenticated.delete(
"/resource/:resourceId/rule/:ruleId",
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.deleteResourceRule),
logActionAudit(ActionsEnum.deleteResourceRule),
resource.deleteResourceRule,
resource.deleteResourceRule
);
authenticated.get(
@@ -523,14 +529,14 @@ authenticated.post(
verifyTargetAccess,
verifyUserHasAction(ActionsEnum.updateTarget),
logActionAudit(ActionsEnum.updateTarget),
target.updateTarget,
target.updateTarget
);
authenticated.delete(
"/target/:targetId",
verifyTargetAccess,
verifyUserHasAction(ActionsEnum.deleteTarget),
logActionAudit(ActionsEnum.deleteTarget),
target.deleteTarget,
target.deleteTarget
);
authenticated.put(
@@ -538,7 +544,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createRole),
logActionAudit(ActionsEnum.createRole),
role.createRole,
role.createRole
);
authenticated.get(
"/org/:orgId/roles",
@@ -564,7 +570,7 @@ authenticated.delete(
verifyRoleAccess,
verifyUserHasAction(ActionsEnum.deleteRole),
logActionAudit(ActionsEnum.deleteRole),
role.deleteRole,
role.deleteRole
);
authenticated.post(
"/role/:roleId/add/:userId",
@@ -572,7 +578,7 @@ authenticated.post(
verifyUserAccess,
verifyUserHasAction(ActionsEnum.addUserRole),
logActionAudit(ActionsEnum.addUserRole),
user.addUserRole,
user.addUserRole
);
authenticated.post(
@@ -581,7 +587,7 @@ authenticated.post(
verifyRoleAccess,
verifyUserHasAction(ActionsEnum.setResourceRoles),
logActionAudit(ActionsEnum.setResourceRoles),
resource.setResourceRoles,
resource.setResourceRoles
);
authenticated.post(
@@ -590,7 +596,7 @@ authenticated.post(
verifySetResourceUsers,
verifyUserHasAction(ActionsEnum.setResourceUsers),
logActionAudit(ActionsEnum.setResourceUsers),
resource.setResourceUsers,
resource.setResourceUsers
);
authenticated.post(
@@ -598,7 +604,7 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourcePassword),
logActionAudit(ActionsEnum.setResourcePassword),
resource.setResourcePassword,
resource.setResourcePassword
);
authenticated.post(
@@ -606,7 +612,7 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourcePincode),
logActionAudit(ActionsEnum.setResourcePincode),
resource.setResourcePincode,
resource.setResourcePincode
);
authenticated.post(
@@ -614,7 +620,7 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourceHeaderAuth),
logActionAudit(ActionsEnum.setResourceHeaderAuth),
resource.setResourceHeaderAuth,
resource.setResourceHeaderAuth
);
authenticated.post(
@@ -622,7 +628,7 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.setResourceWhitelist),
logActionAudit(ActionsEnum.setResourceWhitelist),
resource.setResourceWhitelist,
resource.setResourceWhitelist
);
authenticated.get(
@@ -637,7 +643,7 @@ authenticated.post(
verifyResourceAccess,
verifyUserHasAction(ActionsEnum.generateAccessToken),
logActionAudit(ActionsEnum.generateAccessToken),
accessToken.generateAccessToken,
accessToken.generateAccessToken
);
authenticated.delete(
@@ -645,7 +651,7 @@ authenticated.delete(
verifyAccessTokenAccess,
verifyUserHasAction(ActionsEnum.deleteAcessToken),
logActionAudit(ActionsEnum.deleteAcessToken),
accessToken.deleteAccessToken,
accessToken.deleteAccessToken
);
authenticated.get(
@@ -720,7 +726,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createOrgUser),
logActionAudit(ActionsEnum.createOrgUser),
user.createOrgUser,
user.createOrgUser
);
authenticated.post(
@@ -729,7 +735,7 @@ authenticated.post(
verifyUserAccess,
verifyUserHasAction(ActionsEnum.updateOrgUser),
logActionAudit(ActionsEnum.updateOrgUser),
user.updateOrgUser,
user.updateOrgUser
);
authenticated.get("/org/:orgId/user/:userId", verifyOrgAccess, user.getOrgUser);
@@ -753,7 +759,7 @@ authenticated.delete(
verifyUserAccess,
verifyUserHasAction(ActionsEnum.removeUser),
logActionAudit(ActionsEnum.removeUser),
user.removeUserOrg,
user.removeUserOrg
);
// authenticated.put(
@@ -910,7 +916,7 @@ authenticated.post(
verifyApiKeyAccess,
verifyUserHasAction(ActionsEnum.setApiKeyActions),
logActionAudit(ActionsEnum.setApiKeyActions),
apiKeys.setApiKeyActions,
apiKeys.setApiKeyActions
);
authenticated.get(
@@ -926,7 +932,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createApiKey),
logActionAudit(ActionsEnum.createApiKey),
apiKeys.createOrgApiKey,
apiKeys.createOrgApiKey
);
authenticated.delete(
@@ -935,7 +941,7 @@ authenticated.delete(
verifyApiKeyAccess,
verifyUserHasAction(ActionsEnum.deleteApiKey),
logActionAudit(ActionsEnum.deleteApiKey),
apiKeys.deleteOrgApiKey,
apiKeys.deleteOrgApiKey
);
authenticated.get(
@@ -951,7 +957,7 @@ authenticated.put(
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createOrgDomain),
logActionAudit(ActionsEnum.createOrgDomain),
domain.createOrgDomain,
domain.createOrgDomain
);
authenticated.post(
@@ -960,7 +966,7 @@ authenticated.post(
verifyDomainAccess,
verifyUserHasAction(ActionsEnum.restartOrgDomain),
logActionAudit(ActionsEnum.restartOrgDomain),
domain.restartOrgDomain,
domain.restartOrgDomain
);
authenticated.delete(
@@ -969,7 +975,7 @@ authenticated.delete(
verifyDomainAccess,
verifyUserHasAction(ActionsEnum.deleteOrgDomain),
logActionAudit(ActionsEnum.deleteOrgDomain),
domain.deleteAccountDomain,
domain.deleteAccountDomain
);
authenticated.get(
@@ -979,6 +985,13 @@ authenticated.get(
logs.queryRequestAuditLogs
);
authenticated.get(
"/org/:orgId/logs/analytics",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.viewLogs),
logs.queryRequestAnalytics
);
authenticated.get(
"/org/:orgId/logs/request/export",
verifyOrgAccess,

View File

@@ -29,3 +29,4 @@ export * from "./addRoleToResource";
export * from "./removeRoleFromResource";
export * from "./addUserToResource";
export * from "./removeUserFromResource";
export * from "./listAllResourceNames";

View File

@@ -0,0 +1,90 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db, resourceHeaderAuth } from "@server/db";
import {
resources,
userResources,
roleResources,
resourcePassword,
resourcePincode,
targets,
targetHealthCheck
} from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { sql, eq, or, inArray, and, count } from "drizzle-orm";
import logger from "@server/logger";
import { fromZodError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import type {
ResourceWithTargets,
ListResourcesResponse
} from "./listResources";
const listResourcesParamsSchema = z.strictObject({
orgId: z.string()
});
function queryResourceNames(orgId: string) {
return db
.select({
resourceId: resources.resourceId,
name: resources.name
})
.from(resources)
.where(eq(resources.orgId, orgId));
}
export type ListResourceNamesResponse = Awaited<
ReturnType<typeof queryResourceNames>
>;
registry.registerPath({
method: "get",
path: "/org/{orgId}/resources-names",
description: "List all resource names for an organization.",
tags: [OpenAPITags.Org, OpenAPITags.Resource],
request: {
params: z.object({
orgId: z.string()
})
},
responses: {}
});
export async function listAllResourceNames(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = listResourcesParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromZodError(parsedParams.error)
)
);
}
const orgId = parsedParams.data.orgId;
const data = await queryResourceNames(orgId);
return response<ListResourceNamesResponse>(res, {
data,
success: true,
error: false,
message: "Resource Names retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -8,21 +8,19 @@ import {
resourcePassword,
resourcePincode,
targets,
targetHealthCheck,
targetHealthCheck
} from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { sql, eq, or, inArray, and, count } from "drizzle-orm";
import logger from "@server/logger";
import stoi from "@server/lib/stoi";
import { fromZodError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { warn } from "console";
const listResourcesParamsSchema = z.strictObject({
orgId: z.string()
});
orgId: z.string()
});
const listResourcesSchema = z.object({
limit: z
@@ -67,7 +65,7 @@ type JoinedRow = {
hcEnabled: boolean | null;
};
// grouped by resource with targets[])
// grouped by resource with targets[])
export type ResourceWithTargets = {
resourceId: number;
name: string;
@@ -89,7 +87,7 @@ export type ResourceWithTargets = {
ip: string;
port: number;
enabled: boolean;
healthStatus?: 'healthy' | 'unhealthy' | 'unknown';
healthStatus?: "healthy" | "unhealthy" | "unknown";
}>;
};
@@ -118,7 +116,7 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
targetEnabled: targets.enabled,
hcHealth: targetHealthCheck.hcHealth,
hcEnabled: targetHealthCheck.hcEnabled,
hcEnabled: targetHealthCheck.hcEnabled
})
.from(resources)
.leftJoin(
@@ -273,16 +271,25 @@ export async function listResources(
enabled: row.enabled,
domainId: row.domainId,
headerAuthId: row.headerAuthId,
targets: [],
targets: []
};
map.set(row.resourceId, entry);
}
if (row.targetId != null && row.targetIp && row.targetPort != null && row.targetEnabled != null) {
let healthStatus: 'healthy' | 'unhealthy' | 'unknown' = 'unknown';
if (
row.targetId != null &&
row.targetIp &&
row.targetPort != null &&
row.targetEnabled != null
) {
let healthStatus: "healthy" | "unhealthy" | "unknown" =
"unknown";
if (row.hcEnabled && row.hcHealth) {
healthStatus = row.hcHealth as 'healthy' | 'unhealthy' | 'unknown';
healthStatus = row.hcHealth as
| "healthy"
| "unhealthy"
| "unknown";
}
entry.targets.push({
@@ -290,7 +297,7 @@ export async function listResources(
ip: row.targetIp,
port: row.targetPort,
enabled: row.targetEnabled,
healthStatus: healthStatus,
healthStatus: healthStatus
});
}
}