mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-26 23:06:37 +00:00
add site targets, client resources, and auto login
This commit is contained in:
@@ -15,7 +15,6 @@ import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import stoi from "@server/lib/stoi";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import logger from "@server/logger";
|
||||
import { subdomainSchema } from "@server/lib/schemas";
|
||||
@@ -25,7 +24,6 @@ import { build } from "@server/build";
|
||||
|
||||
const createResourceParamsSchema = z
|
||||
.object({
|
||||
siteId: z.string().transform(stoi).pipe(z.number().int().positive()),
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
@@ -34,7 +32,6 @@ const createHttpResourceSchema = z
|
||||
.object({
|
||||
name: z.string().min(1).max(255),
|
||||
subdomain: z.string().nullable().optional(),
|
||||
siteId: z.number(),
|
||||
http: z.boolean(),
|
||||
protocol: z.enum(["tcp", "udp"]),
|
||||
domainId: z.string()
|
||||
@@ -53,11 +50,10 @@ const createHttpResourceSchema = z
|
||||
const createRawResourceSchema = z
|
||||
.object({
|
||||
name: z.string().min(1).max(255),
|
||||
siteId: z.number(),
|
||||
http: z.boolean(),
|
||||
protocol: z.enum(["tcp", "udp"]),
|
||||
proxyPort: z.number().int().min(1).max(65535),
|
||||
enableProxy: z.boolean().default(true)
|
||||
// enableProxy: z.boolean().default(true) // always true now
|
||||
})
|
||||
.strict()
|
||||
.refine(
|
||||
@@ -78,7 +74,7 @@ export type CreateResourceResponse = Resource;
|
||||
|
||||
registry.registerPath({
|
||||
method: "put",
|
||||
path: "/org/{orgId}/site/{siteId}/resource",
|
||||
path: "/org/{orgId}/resource",
|
||||
description: "Create a resource.",
|
||||
tags: [OpenAPITags.Org, OpenAPITags.Resource],
|
||||
request: {
|
||||
@@ -111,7 +107,7 @@ export async function createResource(
|
||||
);
|
||||
}
|
||||
|
||||
const { siteId, orgId } = parsedParams.data;
|
||||
const { orgId } = parsedParams.data;
|
||||
|
||||
if (req.user && !req.userOrgRoleId) {
|
||||
return next(
|
||||
@@ -146,7 +142,7 @@ export async function createResource(
|
||||
if (http) {
|
||||
return await createHttpResource(
|
||||
{ req, res, next },
|
||||
{ siteId, orgId }
|
||||
{ orgId }
|
||||
);
|
||||
} else {
|
||||
if (
|
||||
@@ -162,7 +158,7 @@ export async function createResource(
|
||||
}
|
||||
return await createRawResource(
|
||||
{ req, res, next },
|
||||
{ siteId, orgId }
|
||||
{ orgId }
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -180,12 +176,11 @@ async function createHttpResource(
|
||||
next: NextFunction;
|
||||
},
|
||||
meta: {
|
||||
siteId: number;
|
||||
orgId: string;
|
||||
}
|
||||
) {
|
||||
const { req, res, next } = route;
|
||||
const { siteId, orgId } = meta;
|
||||
const { orgId } = meta;
|
||||
|
||||
const parsedBody = createHttpResourceSchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
@@ -292,7 +287,6 @@ async function createHttpResource(
|
||||
const newResource = await trx
|
||||
.insert(resources)
|
||||
.values({
|
||||
siteId,
|
||||
fullDomain,
|
||||
domainId,
|
||||
orgId,
|
||||
@@ -357,12 +351,11 @@ async function createRawResource(
|
||||
next: NextFunction;
|
||||
},
|
||||
meta: {
|
||||
siteId: number;
|
||||
orgId: string;
|
||||
}
|
||||
) {
|
||||
const { req, res, next } = route;
|
||||
const { siteId, orgId } = meta;
|
||||
const { orgId } = meta;
|
||||
|
||||
const parsedBody = createRawResourceSchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
@@ -374,7 +367,7 @@ async function createRawResource(
|
||||
);
|
||||
}
|
||||
|
||||
const { name, http, protocol, proxyPort, enableProxy } = parsedBody.data;
|
||||
const { name, http, protocol, proxyPort } = 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
|
||||
@@ -402,13 +395,12 @@ async function createRawResource(
|
||||
const newResource = await trx
|
||||
.insert(resources)
|
||||
.values({
|
||||
siteId,
|
||||
orgId,
|
||||
name,
|
||||
http,
|
||||
protocol,
|
||||
proxyPort,
|
||||
enableProxy
|
||||
// enableProxy
|
||||
})
|
||||
.returning();
|
||||
|
||||
|
||||
@@ -71,44 +71,44 @@ export async function deleteResource(
|
||||
);
|
||||
}
|
||||
|
||||
const [site] = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.siteId, deletedResource.siteId!))
|
||||
.limit(1);
|
||||
|
||||
if (!site) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Site with ID ${deletedResource.siteId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (site.pubKey) {
|
||||
if (site.type == "wireguard") {
|
||||
await addPeer(site.exitNodeId!, {
|
||||
publicKey: site.pubKey,
|
||||
allowedIps: await getAllowedIps(site.siteId)
|
||||
});
|
||||
} else if (site.type == "newt") {
|
||||
// get the newt on the site by querying the newt table for siteId
|
||||
const [newt] = await db
|
||||
.select()
|
||||
.from(newts)
|
||||
.where(eq(newts.siteId, site.siteId))
|
||||
.limit(1);
|
||||
|
||||
removeTargets(
|
||||
newt.newtId,
|
||||
targetsToBeRemoved,
|
||||
deletedResource.protocol,
|
||||
deletedResource.proxyPort
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// const [site] = await db
|
||||
// .select()
|
||||
// .from(sites)
|
||||
// .where(eq(sites.siteId, deletedResource.siteId!))
|
||||
// .limit(1);
|
||||
//
|
||||
// if (!site) {
|
||||
// return next(
|
||||
// createHttpError(
|
||||
// HttpCode.NOT_FOUND,
|
||||
// `Site with ID ${deletedResource.siteId} not found`
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// if (site.pubKey) {
|
||||
// if (site.type == "wireguard") {
|
||||
// await addPeer(site.exitNodeId!, {
|
||||
// publicKey: site.pubKey,
|
||||
// allowedIps: await getAllowedIps(site.siteId)
|
||||
// });
|
||||
// } else if (site.type == "newt") {
|
||||
// // get the newt on the site by querying the newt table for siteId
|
||||
// const [newt] = await db
|
||||
// .select()
|
||||
// .from(newts)
|
||||
// .where(eq(newts.siteId, site.siteId))
|
||||
// .limit(1);
|
||||
//
|
||||
// removeTargets(
|
||||
// newt.newtId,
|
||||
// targetsToBeRemoved,
|
||||
// deletedResource.protocol,
|
||||
// deletedResource.proxyPort
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
return response(res, {
|
||||
data: null,
|
||||
success: true,
|
||||
|
||||
@@ -19,9 +19,7 @@ const getResourceSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type GetResourceResponse = Resource & {
|
||||
siteName: string;
|
||||
};
|
||||
export type GetResourceResponse = Resource;
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
@@ -56,11 +54,9 @@ export async function getResource(
|
||||
.select()
|
||||
.from(resources)
|
||||
.where(eq(resources.resourceId, resourceId))
|
||||
.leftJoin(sites, eq(sites.siteId, resources.siteId))
|
||||
.limit(1);
|
||||
|
||||
const resource = resp.resources;
|
||||
const site = resp.sites;
|
||||
const resource = resp;
|
||||
|
||||
if (!resource) {
|
||||
return next(
|
||||
@@ -73,8 +69,7 @@ export async function getResource(
|
||||
|
||||
return response(res, {
|
||||
data: {
|
||||
...resource,
|
||||
siteName: site?.name
|
||||
...resource
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
|
||||
@@ -31,6 +31,7 @@ export type GetResourceAuthInfoResponse = {
|
||||
blockAccess: boolean;
|
||||
url: string;
|
||||
whitelist: boolean;
|
||||
skipToIdpId: number | null;
|
||||
};
|
||||
|
||||
export async function getResourceAuthInfo(
|
||||
@@ -86,7 +87,8 @@ export async function getResourceAuthInfo(
|
||||
sso: resource.sso,
|
||||
blockAccess: resource.blockAccess,
|
||||
url,
|
||||
whitelist: resource.emailWhitelistEnabled
|
||||
whitelist: resource.emailWhitelistEnabled,
|
||||
skipToIdpId: resource.skipToIdpId
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
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,
|
||||
import {
|
||||
resources,
|
||||
userResources,
|
||||
roleResources,
|
||||
userOrgs,
|
||||
resourcePassword,
|
||||
resourcePincode,
|
||||
resourceWhitelist,
|
||||
sites
|
||||
resourceWhitelist
|
||||
} from "@server/db";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -37,12 +35,7 @@ export async function getUserResources(
|
||||
roleId: userOrgs.roleId
|
||||
})
|
||||
.from(userOrgs)
|
||||
.where(
|
||||
and(
|
||||
eq(userOrgs.userId, userId),
|
||||
eq(userOrgs.orgId, orgId)
|
||||
)
|
||||
)
|
||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)))
|
||||
.limit(1);
|
||||
|
||||
if (userOrgResult.length === 0) {
|
||||
@@ -71,8 +64,8 @@ export async function getUserResources(
|
||||
|
||||
// Combine all accessible resource IDs
|
||||
const accessibleResourceIds = [
|
||||
...directResources.map(r => r.resourceId),
|
||||
...roleResourceResults.map(r => r.resourceId)
|
||||
...directResources.map((r) => r.resourceId),
|
||||
...roleResourceResults.map((r) => r.resourceId)
|
||||
];
|
||||
|
||||
if (accessibleResourceIds.length === 0) {
|
||||
@@ -95,11 +88,9 @@ export async function getUserResources(
|
||||
enabled: resources.enabled,
|
||||
sso: resources.sso,
|
||||
protocol: resources.protocol,
|
||||
emailWhitelistEnabled: resources.emailWhitelistEnabled,
|
||||
siteName: sites.name
|
||||
emailWhitelistEnabled: resources.emailWhitelistEnabled
|
||||
})
|
||||
.from(resources)
|
||||
.leftJoin(sites, eq(sites.siteId, resources.siteId))
|
||||
.where(
|
||||
and(
|
||||
inArray(resources.resourceId, accessibleResourceIds),
|
||||
@@ -111,28 +102,61 @@ export async function getUserResources(
|
||||
// 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 [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;
|
||||
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),
|
||||
protected: !!(
|
||||
resource.sso ||
|
||||
hasPassword ||
|
||||
hasPincode ||
|
||||
hasWhitelist
|
||||
),
|
||||
protocol: resource.protocol,
|
||||
sso: resource.sso,
|
||||
password: hasPassword,
|
||||
pincode: hasPincode,
|
||||
whitelist: hasWhitelist,
|
||||
siteName: resource.siteName
|
||||
whitelist: hasWhitelist
|
||||
};
|
||||
})
|
||||
);
|
||||
@@ -144,11 +168,13 @@ export async function getUserResources(
|
||||
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")
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Internal server error"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -165,4 +191,4 @@ export type GetUserResourcesResponse = {
|
||||
protocol: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -16,10 +16,9 @@ export * from "./setResourceWhitelist";
|
||||
export * from "./getResourceWhitelist";
|
||||
export * from "./authWithWhitelist";
|
||||
export * from "./authWithAccessToken";
|
||||
export * from "./transferResource";
|
||||
export * from "./getExchangeToken";
|
||||
export * from "./createResourceRule";
|
||||
export * from "./deleteResourceRule";
|
||||
export * from "./listResourceRules";
|
||||
export * from "./updateResourceRule";
|
||||
export * from "./getUserResources";
|
||||
export * from "./getUserResources";
|
||||
|
||||
@@ -3,7 +3,6 @@ import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import {
|
||||
resources,
|
||||
sites,
|
||||
userResources,
|
||||
roleResources,
|
||||
resourcePassword,
|
||||
@@ -20,17 +19,9 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const listResourcesParamsSchema = z
|
||||
.object({
|
||||
siteId: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform(stoi)
|
||||
.pipe(z.number().int().positive().optional()),
|
||||
orgId: z.string().optional()
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict()
|
||||
.refine((data) => !!data.siteId !== !!data.orgId, {
|
||||
message: "Either siteId or orgId must be provided, but not both"
|
||||
});
|
||||
.strict();
|
||||
|
||||
const listResourcesSchema = z.object({
|
||||
limit: z
|
||||
@@ -48,82 +39,38 @@ const listResourcesSchema = z.object({
|
||||
.pipe(z.number().int().nonnegative())
|
||||
});
|
||||
|
||||
function queryResources(
|
||||
accessibleResourceIds: number[],
|
||||
siteId?: number,
|
||||
orgId?: string
|
||||
) {
|
||||
if (siteId) {
|
||||
return db
|
||||
.select({
|
||||
resourceId: resources.resourceId,
|
||||
name: resources.name,
|
||||
fullDomain: resources.fullDomain,
|
||||
ssl: resources.ssl,
|
||||
siteName: sites.name,
|
||||
siteId: sites.niceId,
|
||||
passwordId: resourcePassword.passwordId,
|
||||
pincodeId: resourcePincode.pincodeId,
|
||||
sso: resources.sso,
|
||||
whitelist: resources.emailWhitelistEnabled,
|
||||
http: resources.http,
|
||||
protocol: resources.protocol,
|
||||
proxyPort: resources.proxyPort,
|
||||
enabled: resources.enabled,
|
||||
domainId: resources.domainId
|
||||
})
|
||||
.from(resources)
|
||||
.leftJoin(sites, eq(resources.siteId, sites.siteId))
|
||||
.leftJoin(
|
||||
resourcePassword,
|
||||
eq(resourcePassword.resourceId, resources.resourceId)
|
||||
function queryResources(accessibleResourceIds: number[], orgId: string) {
|
||||
return db
|
||||
.select({
|
||||
resourceId: resources.resourceId,
|
||||
name: resources.name,
|
||||
ssl: resources.ssl,
|
||||
fullDomain: resources.fullDomain,
|
||||
passwordId: resourcePassword.passwordId,
|
||||
sso: resources.sso,
|
||||
pincodeId: resourcePincode.pincodeId,
|
||||
whitelist: resources.emailWhitelistEnabled,
|
||||
http: resources.http,
|
||||
protocol: resources.protocol,
|
||||
proxyPort: resources.proxyPort,
|
||||
enabled: resources.enabled,
|
||||
domainId: resources.domainId
|
||||
})
|
||||
.from(resources)
|
||||
.leftJoin(
|
||||
resourcePassword,
|
||||
eq(resourcePassword.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePincode,
|
||||
eq(resourcePincode.resourceId, resources.resourceId)
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
inArray(resources.resourceId, accessibleResourceIds),
|
||||
eq(resources.orgId, orgId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePincode,
|
||||
eq(resourcePincode.resourceId, resources.resourceId)
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
inArray(resources.resourceId, accessibleResourceIds),
|
||||
eq(resources.siteId, siteId)
|
||||
)
|
||||
);
|
||||
} else if (orgId) {
|
||||
return db
|
||||
.select({
|
||||
resourceId: resources.resourceId,
|
||||
name: resources.name,
|
||||
ssl: resources.ssl,
|
||||
fullDomain: resources.fullDomain,
|
||||
siteName: sites.name,
|
||||
siteId: sites.niceId,
|
||||
passwordId: resourcePassword.passwordId,
|
||||
sso: resources.sso,
|
||||
pincodeId: resourcePincode.pincodeId,
|
||||
whitelist: resources.emailWhitelistEnabled,
|
||||
http: resources.http,
|
||||
protocol: resources.protocol,
|
||||
proxyPort: resources.proxyPort,
|
||||
enabled: resources.enabled,
|
||||
domainId: resources.domainId
|
||||
})
|
||||
.from(resources)
|
||||
.leftJoin(sites, eq(resources.siteId, sites.siteId))
|
||||
.leftJoin(
|
||||
resourcePassword,
|
||||
eq(resourcePassword.resourceId, resources.resourceId)
|
||||
)
|
||||
.leftJoin(
|
||||
resourcePincode,
|
||||
eq(resourcePincode.resourceId, resources.resourceId)
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
inArray(resources.resourceId, accessibleResourceIds),
|
||||
eq(resources.orgId, orgId)
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export type ListResourcesResponse = {
|
||||
@@ -131,20 +78,6 @@ export type ListResourcesResponse = {
|
||||
pagination: { total: number; limit: number; offset: number };
|
||||
};
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/site/{siteId}/resources",
|
||||
description: "List resources for a site.",
|
||||
tags: [OpenAPITags.Site, OpenAPITags.Resource],
|
||||
request: {
|
||||
params: z.object({
|
||||
siteId: z.number()
|
||||
}),
|
||||
query: listResourcesSchema
|
||||
},
|
||||
responses: {}
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/org/{orgId}/resources",
|
||||
@@ -185,9 +118,11 @@ export async function listResources(
|
||||
)
|
||||
);
|
||||
}
|
||||
const { siteId } = parsedParams.data;
|
||||
|
||||
const orgId = parsedParams.data.orgId || req.userOrg?.orgId || req.apiKeyOrg?.orgId;
|
||||
const orgId =
|
||||
parsedParams.data.orgId ||
|
||||
req.userOrg?.orgId ||
|
||||
req.apiKeyOrg?.orgId;
|
||||
|
||||
if (!orgId) {
|
||||
return next(
|
||||
@@ -207,24 +142,27 @@ export async function listResources(
|
||||
let accessibleResources;
|
||||
if (req.user) {
|
||||
accessibleResources = await db
|
||||
.select({
|
||||
resourceId: sql<number>`COALESCE(${userResources.resourceId}, ${roleResources.resourceId})`
|
||||
})
|
||||
.from(userResources)
|
||||
.fullJoin(
|
||||
roleResources,
|
||||
eq(userResources.resourceId, roleResources.resourceId)
|
||||
)
|
||||
.where(
|
||||
or(
|
||||
eq(userResources.userId, req.user!.userId),
|
||||
eq(roleResources.roleId, req.userOrgRoleId!)
|
||||
.select({
|
||||
resourceId: sql<number>`COALESCE(${userResources.resourceId}, ${roleResources.resourceId})`
|
||||
})
|
||||
.from(userResources)
|
||||
.fullJoin(
|
||||
roleResources,
|
||||
eq(userResources.resourceId, roleResources.resourceId)
|
||||
)
|
||||
);
|
||||
.where(
|
||||
or(
|
||||
eq(userResources.userId, req.user!.userId),
|
||||
eq(roleResources.roleId, req.userOrgRoleId!)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
accessibleResources = await db.select({
|
||||
resourceId: resources.resourceId
|
||||
}).from(resources).where(eq(resources.orgId, orgId));
|
||||
accessibleResources = await db
|
||||
.select({
|
||||
resourceId: resources.resourceId
|
||||
})
|
||||
.from(resources)
|
||||
.where(eq(resources.orgId, orgId));
|
||||
}
|
||||
|
||||
const accessibleResourceIds = accessibleResources.map(
|
||||
@@ -236,7 +174,7 @@ export async function listResources(
|
||||
.from(resources)
|
||||
.where(inArray(resources.resourceId, accessibleResourceIds));
|
||||
|
||||
const baseQuery = queryResources(accessibleResourceIds, siteId, orgId);
|
||||
const baseQuery = queryResources(accessibleResourceIds, orgId);
|
||||
|
||||
const resourcesList = await baseQuery!.limit(limit).offset(offset);
|
||||
const totalCountResult = await countQuery;
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { newts, resources, sites, targets } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { addPeer } from "../gerbil/peers";
|
||||
import { addTargets, removeTargets } from "../newt/targets";
|
||||
import { getAllowedIps } from "../target/helpers";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const transferResourceParamsSchema = z
|
||||
.object({
|
||||
resourceId: z
|
||||
.string()
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive())
|
||||
})
|
||||
.strict();
|
||||
|
||||
const transferResourceBodySchema = z
|
||||
.object({
|
||||
siteId: z.number().int().positive()
|
||||
})
|
||||
.strict();
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
path: "/resource/{resourceId}/transfer",
|
||||
description:
|
||||
"Transfer a resource to a different site. This will also transfer the targets associated with the resource.",
|
||||
tags: [OpenAPITags.Resource],
|
||||
request: {
|
||||
params: transferResourceParamsSchema,
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: transferResourceBodySchema
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
});
|
||||
|
||||
export async function transferResource(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedParams = transferResourceParamsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const parsedBody = transferResourceBodySchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedBody.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { resourceId } = parsedParams.data;
|
||||
const { siteId } = parsedBody.data;
|
||||
|
||||
const [oldResource] = await db
|
||||
.select()
|
||||
.from(resources)
|
||||
.where(eq(resources.resourceId, resourceId))
|
||||
.limit(1);
|
||||
|
||||
if (!oldResource) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Resource with ID ${resourceId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (oldResource.siteId === siteId) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
`Resource is already assigned to this site`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [newSite] = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.siteId, siteId))
|
||||
.limit(1);
|
||||
|
||||
if (!newSite) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Site with ID ${siteId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [oldSite] = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.siteId, oldResource.siteId))
|
||||
.limit(1);
|
||||
|
||||
if (!oldSite) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Site with ID ${oldResource.siteId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [updatedResource] = await db
|
||||
.update(resources)
|
||||
.set({ siteId })
|
||||
.where(eq(resources.resourceId, resourceId))
|
||||
.returning();
|
||||
|
||||
if (!updatedResource) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Resource with ID ${resourceId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const resourceTargets = await db
|
||||
.select()
|
||||
.from(targets)
|
||||
.where(eq(targets.resourceId, resourceId));
|
||||
|
||||
if (resourceTargets.length > 0) {
|
||||
////// REMOVE THE TARGETS FROM THE OLD SITE //////
|
||||
if (oldSite.pubKey) {
|
||||
if (oldSite.type == "wireguard") {
|
||||
await addPeer(oldSite.exitNodeId!, {
|
||||
publicKey: oldSite.pubKey,
|
||||
allowedIps: await getAllowedIps(oldSite.siteId)
|
||||
});
|
||||
} else if (oldSite.type == "newt") {
|
||||
const [newt] = await db
|
||||
.select()
|
||||
.from(newts)
|
||||
.where(eq(newts.siteId, oldSite.siteId))
|
||||
.limit(1);
|
||||
|
||||
removeTargets(
|
||||
newt.newtId,
|
||||
resourceTargets,
|
||||
updatedResource.protocol,
|
||||
updatedResource.proxyPort
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
////// ADD THE TARGETS TO THE NEW SITE //////
|
||||
if (newSite.pubKey) {
|
||||
if (newSite.type == "wireguard") {
|
||||
await addPeer(newSite.exitNodeId!, {
|
||||
publicKey: newSite.pubKey,
|
||||
allowedIps: await getAllowedIps(newSite.siteId)
|
||||
});
|
||||
} else if (newSite.type == "newt") {
|
||||
const [newt] = await db
|
||||
.select()
|
||||
.from(newts)
|
||||
.where(eq(newts.siteId, newSite.siteId))
|
||||
.limit(1);
|
||||
|
||||
addTargets(
|
||||
newt.newtId,
|
||||
resourceTargets,
|
||||
updatedResource.protocol,
|
||||
updatedResource.proxyPort
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response(res, {
|
||||
data: updatedResource,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Resource transferred successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import { tlsNameSchema } from "@server/lib/schemas";
|
||||
import { subdomainSchema } from "@server/lib/schemas";
|
||||
import { registry } from "@server/openApi";
|
||||
import { OpenAPITags } from "@server/openApi";
|
||||
import { build } from "@server/build";
|
||||
|
||||
const updateResourceParamsSchema = z
|
||||
.object({
|
||||
@@ -44,7 +43,8 @@ const updateHttpResourceBodySchema = z
|
||||
enabled: z.boolean().optional(),
|
||||
stickySession: z.boolean().optional(),
|
||||
tlsServerName: z.string().nullable().optional(),
|
||||
setHostHeader: z.string().nullable().optional()
|
||||
setHostHeader: z.string().nullable().optional(),
|
||||
skipToIdpId: z.number().int().positive().nullable().optional()
|
||||
})
|
||||
.strict()
|
||||
.refine((data) => Object.keys(data).length > 0, {
|
||||
@@ -91,8 +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(),
|
||||
enableProxy: z.boolean().optional()
|
||||
enabled: z.boolean().optional()
|
||||
// enableProxy: z.boolean().optional() // always true now
|
||||
})
|
||||
.strict()
|
||||
.refine((data) => Object.keys(data).length > 0, {
|
||||
|
||||
Reference in New Issue
Block a user