Merge branch 'main' into copilot/fix-1112

This commit is contained in:
Owen
2025-08-10 10:10:10 -07:00
77 changed files with 4113 additions and 1256 deletions

View File

@@ -33,10 +33,7 @@ const createResourceParamsSchema = z
const createHttpResourceSchema = z
.object({
name: z.string().min(1).max(255),
subdomain: z
.string()
.nullable()
.optional(),
subdomain: z.string().nullable().optional(),
siteId: z.number(),
http: z.boolean(),
protocol: z.enum(["tcp", "udp"]),
@@ -59,7 +56,8 @@ const createRawResourceSchema = z
siteId: z.number(),
http: z.boolean(),
protocol: z.enum(["tcp", "udp"]),
proxyPort: z.number().int().min(1).max(65535)
proxyPort: z.number().int().min(1).max(65535),
enableProxy: z.boolean().default(true)
})
.strict()
.refine(
@@ -88,12 +86,7 @@ registry.registerPath({
body: {
content: {
"application/json": {
schema:
build == "oss"
? createHttpResourceSchema.or(
createRawResourceSchema
)
: createHttpResourceSchema
schema: createHttpResourceSchema.or(createRawResourceSchema)
}
}
}
@@ -156,7 +149,10 @@ export async function createResource(
{ siteId, orgId }
);
} else {
if (!config.getRawConfig().flags?.allow_raw_resources && build == "oss") {
if (
!config.getRawConfig().flags?.allow_raw_resources &&
build == "oss"
) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
@@ -378,7 +374,7 @@ async function createRawResource(
);
}
const { name, http, protocol, proxyPort } = parsedBody.data;
const { name, http, protocol, proxyPort, enableProxy } = parsedBody.data;
// if http is false check to see if there is already a resource with the same port and protocol
const existingResource = await db
@@ -411,7 +407,8 @@ async function createRawResource(
name,
http,
protocol,
proxyPort
proxyPort,
enableProxy
})
.returning();

View File

@@ -103,7 +103,8 @@ export async function deleteResource(
removeTargets(
newt.newtId,
targetsToBeRemoved,
deletedResource.protocol
deletedResource.protocol,
deletedResource.proxyPort
);
}
}

View File

@@ -0,0 +1,168 @@
import { Request, Response, NextFunction } from "express";
import { db } from "@server/db";
import { and, eq, or, inArray } from "drizzle-orm";
import {
resources,
userResources,
roleResources,
userOrgs,
roles,
resourcePassword,
resourcePincode,
resourceWhitelist,
sites
} from "@server/db";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { response } from "@server/lib/response";
export async function getUserResources(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const { orgId } = req.params;
const userId = req.user?.userId;
if (!userId) {
return next(
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
);
}
// First get the user's role in the organization
const userOrgResult = await db
.select({
roleId: userOrgs.roleId
})
.from(userOrgs)
.where(
and(
eq(userOrgs.userId, userId),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
if (userOrgResult.length === 0) {
return next(
createHttpError(HttpCode.FORBIDDEN, "User not in organization")
);
}
const userRoleId = userOrgResult[0].roleId;
// Get resources accessible through direct assignment or role assignment
const directResourcesQuery = db
.select({ resourceId: userResources.resourceId })
.from(userResources)
.where(eq(userResources.userId, userId));
const roleResourcesQuery = db
.select({ resourceId: roleResources.resourceId })
.from(roleResources)
.where(eq(roleResources.roleId, userRoleId));
const [directResources, roleResourceResults] = await Promise.all([
directResourcesQuery,
roleResourcesQuery
]);
// Combine all accessible resource IDs
const accessibleResourceIds = [
...directResources.map(r => r.resourceId),
...roleResourceResults.map(r => r.resourceId)
];
if (accessibleResourceIds.length === 0) {
return response(res, {
data: { resources: [] },
success: true,
error: false,
message: "No resources found",
status: HttpCode.OK
});
}
// Get resource details for accessible resources
const resourcesData = await db
.select({
resourceId: resources.resourceId,
name: resources.name,
fullDomain: resources.fullDomain,
ssl: resources.ssl,
enabled: resources.enabled,
sso: resources.sso,
protocol: resources.protocol,
emailWhitelistEnabled: resources.emailWhitelistEnabled,
siteName: sites.name
})
.from(resources)
.leftJoin(sites, eq(sites.siteId, resources.siteId))
.where(
and(
inArray(resources.resourceId, accessibleResourceIds),
eq(resources.orgId, orgId),
eq(resources.enabled, true)
)
);
// Check for password, pincode, and whitelist protection for each resource
const resourcesWithAuth = await Promise.all(
resourcesData.map(async (resource) => {
const [passwordCheck, pincodeCheck, whitelistCheck] = await Promise.all([
db.select().from(resourcePassword).where(eq(resourcePassword.resourceId, resource.resourceId)).limit(1),
db.select().from(resourcePincode).where(eq(resourcePincode.resourceId, resource.resourceId)).limit(1),
db.select().from(resourceWhitelist).where(eq(resourceWhitelist.resourceId, resource.resourceId)).limit(1)
]);
const hasPassword = passwordCheck.length > 0;
const hasPincode = pincodeCheck.length > 0;
const hasWhitelist = whitelistCheck.length > 0 || resource.emailWhitelistEnabled;
return {
resourceId: resource.resourceId,
name: resource.name,
domain: `${resource.ssl ? "https://" : "http://"}${resource.fullDomain}`,
enabled: resource.enabled,
protected: !!(resource.sso || hasPassword || hasPincode || hasWhitelist),
protocol: resource.protocol,
sso: resource.sso,
password: hasPassword,
pincode: hasPincode,
whitelist: hasWhitelist,
siteName: resource.siteName
};
})
);
return response(res, {
data: { resources: resourcesWithAuth },
success: true,
error: false,
message: "User resources retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
console.error("Error fetching user resources:", error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "Internal server error")
);
}
}
export type GetUserResourcesResponse = {
success: boolean;
data: {
resources: Array<{
resourceId: number;
name: string;
domain: string;
enabled: boolean;
protected: boolean;
protocol: string;
}>;
};
};

View File

@@ -21,4 +21,5 @@ export * from "./getExchangeToken";
export * from "./createResourceRule";
export * from "./deleteResourceRule";
export * from "./listResourceRules";
export * from "./updateResourceRule";
export * from "./updateResourceRule";
export * from "./getUserResources";

View File

@@ -168,7 +168,8 @@ export async function transferResource(
removeTargets(
newt.newtId,
resourceTargets,
updatedResource.protocol
updatedResource.protocol,
updatedResource.proxyPort
);
}
}
@@ -190,7 +191,8 @@ export async function transferResource(
addTargets(
newt.newtId,
resourceTargets,
updatedResource.protocol
updatedResource.protocol,
updatedResource.proxyPort
);
}
}

View File

@@ -34,9 +34,7 @@ const updateResourceParamsSchema = z
const updateHttpResourceBodySchema = z
.object({
name: z.string().min(1).max(255).optional(),
subdomain: subdomainSchema
.nullable()
.optional(),
subdomain: subdomainSchema.nullable().optional(),
ssl: z.boolean().optional(),
sso: z.boolean().optional(),
blockAccess: z.boolean().optional(),
@@ -93,7 +91,8 @@ const updateRawResourceBodySchema = z
name: z.string().min(1).max(255).optional(),
proxyPort: z.number().int().min(1).max(65535).optional(),
stickySession: z.boolean().optional(),
enabled: z.boolean().optional()
enabled: z.boolean().optional(),
enableProxy: z.boolean().optional()
})
.strict()
.refine((data) => Object.keys(data).length > 0, {
@@ -121,12 +120,9 @@ registry.registerPath({
body: {
content: {
"application/json": {
schema:
build == "oss"
? updateHttpResourceBodySchema.and(
updateRawResourceBodySchema
)
: updateHttpResourceBodySchema
schema: updateHttpResourceBodySchema.and(
updateRawResourceBodySchema
)
}
}
}
@@ -288,7 +284,9 @@ async function updateHttpResource(
} else if (domainRes.domains.type == "wildcard") {
if (updateData.subdomain !== undefined) {
// the subdomain cant have a dot in it
const parsedSubdomain = subdomainSchema.safeParse(updateData.subdomain);
const parsedSubdomain = subdomainSchema.safeParse(
updateData.subdomain
);
if (!parsedSubdomain.success) {
return next(
createHttpError(
@@ -341,7 +339,7 @@ async function updateHttpResource(
const updatedResource = await db
.update(resources)
.set({...updateData, })
.set({ ...updateData })
.where(eq(resources.resourceId, resource.resourceId))
.returning();