mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-22 16:55:44 +00:00
Add crud for browser targets
This commit is contained in:
@@ -152,7 +152,12 @@ export enum ActionsEnum {
|
|||||||
createHealthCheck = "createHealthCheck",
|
createHealthCheck = "createHealthCheck",
|
||||||
updateHealthCheck = "updateHealthCheck",
|
updateHealthCheck = "updateHealthCheck",
|
||||||
deleteHealthCheck = "deleteHealthCheck",
|
deleteHealthCheck = "deleteHealthCheck",
|
||||||
listHealthChecks = "listHealthChecks"
|
listHealthChecks = "listHealthChecks",
|
||||||
|
createBrowserGatewayTarget = "createBrowserGatewayTarget",
|
||||||
|
updateBrowserGatewayTarget = "updateBrowserGatewayTarget",
|
||||||
|
deleteBrowserGatewayTarget = "deleteBrowserGatewayTarget",
|
||||||
|
getBrowserGatewayTarget = "getBrowserGatewayTarget",
|
||||||
|
listBrowserGatewayTargets = "listBrowserGatewayTargets"
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkUserActionPermission(
|
export async function checkUserActionPermission(
|
||||||
|
|||||||
@@ -592,6 +592,7 @@ export const browserGatewayTarget = pgTable("browserGatewayTarget", {
|
|||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
})
|
})
|
||||||
.notNull(),
|
.notNull(),
|
||||||
|
authToken: varchar("authToken").notNull(),
|
||||||
type: varchar("type").notNull(), // "ssh", "rdp", "vnc"
|
type: varchar("type").notNull(), // "ssh", "rdp", "vnc"
|
||||||
destination: varchar("destination").notNull(),
|
destination: varchar("destination").notNull(),
|
||||||
destinationPort: integer("destinationPort").notNull()
|
destinationPort: integer("destinationPort").notNull()
|
||||||
|
|||||||
@@ -602,6 +602,7 @@ export const browserGatewayTarget = sqliteTable("browserGatewayTarget", {
|
|||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
})
|
})
|
||||||
.notNull(),
|
.notNull(),
|
||||||
|
authToken: text("authToken").notNull(),
|
||||||
type: text("type").notNull(), // "ssh", "rdp", "vnc"
|
type: text("type").notNull(), // "ssh", "rdp", "vnc"
|
||||||
destination: text("destination").notNull(),
|
destination: text("destination").notNull(),
|
||||||
destinationPort: integer("destinationPort").notNull()
|
destinationPort: integer("destinationPort").notNull()
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
browserGatewayTarget,
|
||||||
|
BrowserGatewayTarget,
|
||||||
|
db,
|
||||||
|
newts,
|
||||||
|
resources,
|
||||||
|
sites
|
||||||
|
} from "@server/db";
|
||||||
|
import { eq, and } from "drizzle-orm";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import { encrypt } from "@server/lib/crypto";
|
||||||
|
import config from "@server/lib/config";
|
||||||
|
import { sendBrowserGatewayTargets } from "@server/routers/newt/targets";
|
||||||
|
import { generateId } from "@server/auth/sessions/app";
|
||||||
|
|
||||||
|
const paramsSchema = z.strictObject({
|
||||||
|
orgId: z.string().nonempty(),
|
||||||
|
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
});
|
||||||
|
|
||||||
|
const bodySchema = z.strictObject({
|
||||||
|
siteId: z.number().int().positive(),
|
||||||
|
type: z.enum(["ssh", "rdp", "vnc"]),
|
||||||
|
destination: z.string().nonempty(),
|
||||||
|
destinationPort: z.number().int().min(1).max(65535)
|
||||||
|
});
|
||||||
|
|
||||||
|
export type CreateBrowserGatewayTargetResponse = BrowserGatewayTarget;
|
||||||
|
|
||||||
|
registry.registerPath({
|
||||||
|
method: "put",
|
||||||
|
path: "/org/{orgId}/resource/{resourceId}/browser-gateway-target",
|
||||||
|
description: "Create a browser gateway target for a resource.",
|
||||||
|
tags: [OpenAPITags.Org],
|
||||||
|
request: {
|
||||||
|
params: paramsSchema,
|
||||||
|
body: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: bodySchema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responses: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function createBrowserGatewayTarget(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedParams = paramsSchema.safeParse(req.params);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { orgId, resourceId } = parsedParams.data;
|
||||||
|
|
||||||
|
const parsedBody = bodySchema.safeParse(req.body);
|
||||||
|
if (!parsedBody.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedBody.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { siteId, type, destination, destinationPort } = parsedBody.data;
|
||||||
|
|
||||||
|
const [resource] = await db
|
||||||
|
.select()
|
||||||
|
.from(resources)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(resources.resourceId, resourceId),
|
||||||
|
eq(resources.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!resource) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Resource with ID ${resourceId} not found in organization ${orgId}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [site] = await db
|
||||||
|
.select()
|
||||||
|
.from(sites)
|
||||||
|
.where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!site) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Site with ID ${siteId} not found in organization ${orgId}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const plainToken = generateId(48);
|
||||||
|
const encryptedToken = encrypt(
|
||||||
|
plainToken,
|
||||||
|
config.getRawConfig().server.secret!
|
||||||
|
);
|
||||||
|
|
||||||
|
const [record] = await db
|
||||||
|
.insert(browserGatewayTarget)
|
||||||
|
.values({
|
||||||
|
resourceId,
|
||||||
|
siteId,
|
||||||
|
type,
|
||||||
|
destination,
|
||||||
|
destinationPort,
|
||||||
|
authToken: encryptedToken
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (site.type === "newt") {
|
||||||
|
const [newt] = await db
|
||||||
|
.select()
|
||||||
|
.from(newts)
|
||||||
|
.where(eq(newts.siteId, siteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (newt) {
|
||||||
|
await sendBrowserGatewayTargets(
|
||||||
|
newt.newtId,
|
||||||
|
[record],
|
||||||
|
newt.version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Created browser gateway target ${record.browserGatewayTargetId} for resource ${resourceId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return response<CreateBrowserGatewayTargetResponse>(res, {
|
||||||
|
data: record,
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Browser gateway target created successfully",
|
||||||
|
status: HttpCode.CREATED
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Failed to create browser gateway target"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { browserGatewayTarget, db, newts, sites } from "@server/db";
|
||||||
|
import { eq, and } from "drizzle-orm";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import { removeBrowserGatewayTarget } from "@server/routers/newt/targets";
|
||||||
|
|
||||||
|
const paramsSchema = z.strictObject({
|
||||||
|
orgId: z.string().nonempty(),
|
||||||
|
browserGatewayTargetId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
});
|
||||||
|
|
||||||
|
registry.registerPath({
|
||||||
|
method: "delete",
|
||||||
|
path: "/org/{orgId}/browser-gateway-target/{browserGatewayTargetId}",
|
||||||
|
description: "Delete a browser gateway target.",
|
||||||
|
tags: [OpenAPITags.Org],
|
||||||
|
request: {
|
||||||
|
params: paramsSchema
|
||||||
|
},
|
||||||
|
responses: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function deleteBrowserGatewayTarget(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedParams = paramsSchema.safeParse(req.params);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { orgId, browserGatewayTargetId } = parsedParams.data;
|
||||||
|
|
||||||
|
const [existing] = await db
|
||||||
|
.select({ bgt: browserGatewayTarget, site: sites })
|
||||||
|
.from(browserGatewayTarget)
|
||||||
|
.innerJoin(sites, eq(sites.siteId, browserGatewayTarget.siteId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(
|
||||||
|
browserGatewayTarget.browserGatewayTargetId,
|
||||||
|
browserGatewayTargetId
|
||||||
|
),
|
||||||
|
eq(sites.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Browser gateway target with ID ${browserGatewayTargetId} not found`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.delete(browserGatewayTarget)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
browserGatewayTarget.browserGatewayTargetId,
|
||||||
|
browserGatewayTargetId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existing.site.type === "newt") {
|
||||||
|
const [newt] = await db
|
||||||
|
.select()
|
||||||
|
.from(newts)
|
||||||
|
.where(eq(newts.siteId, existing.bgt.siteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (newt) {
|
||||||
|
await removeBrowserGatewayTarget(
|
||||||
|
newt.newtId,
|
||||||
|
browserGatewayTargetId,
|
||||||
|
newt.version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Deleted browser gateway target ${browserGatewayTargetId}`);
|
||||||
|
|
||||||
|
return response(res, {
|
||||||
|
data: null,
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Browser gateway target deleted successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Failed to delete browser gateway target"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
browserGatewayTarget,
|
||||||
|
BrowserGatewayTarget,
|
||||||
|
db,
|
||||||
|
sites
|
||||||
|
} from "@server/db";
|
||||||
|
import { eq, and } from "drizzle-orm";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
|
||||||
|
const paramsSchema = z.strictObject({
|
||||||
|
orgId: z.string().nonempty(),
|
||||||
|
browserGatewayTargetId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GetBrowserGatewayTargetResponse = BrowserGatewayTarget;
|
||||||
|
|
||||||
|
registry.registerPath({
|
||||||
|
method: "get",
|
||||||
|
path: "/org/{orgId}/browser-gateway-target/{browserGatewayTargetId}",
|
||||||
|
description: "Get a browser gateway target.",
|
||||||
|
tags: [OpenAPITags.Org],
|
||||||
|
request: {
|
||||||
|
params: paramsSchema
|
||||||
|
},
|
||||||
|
responses: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function getBrowserGatewayTarget(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedParams = paramsSchema.safeParse(req.params);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { orgId, browserGatewayTargetId } = parsedParams.data;
|
||||||
|
|
||||||
|
const [result] = await db
|
||||||
|
.select({ bgt: browserGatewayTarget })
|
||||||
|
.from(browserGatewayTarget)
|
||||||
|
.innerJoin(sites, eq(sites.siteId, browserGatewayTarget.siteId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(
|
||||||
|
browserGatewayTarget.browserGatewayTargetId,
|
||||||
|
browserGatewayTargetId
|
||||||
|
),
|
||||||
|
eq(sites.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Browser gateway target with ID ${browserGatewayTargetId} not found`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response<GetBrowserGatewayTargetResponse>(res, {
|
||||||
|
data: result.bgt,
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Browser gateway target retrieved successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Failed to retrieve browser gateway target"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
server/private/routers/browserGatewayTarget/index.ts
Normal file
18
server/private/routers/browserGatewayTarget/index.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./createBrowserGatewayTarget";
|
||||||
|
export * from "./updateBrowserGatewayTarget";
|
||||||
|
export * from "./deleteBrowserGatewayTarget";
|
||||||
|
export * from "./getBrowserGatewayTarget";
|
||||||
|
export * from "./listBrowserGatewayTargets";
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
browserGatewayTarget,
|
||||||
|
BrowserGatewayTarget,
|
||||||
|
db,
|
||||||
|
resources,
|
||||||
|
sites
|
||||||
|
} from "@server/db";
|
||||||
|
import { eq, and } from "drizzle-orm";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
|
||||||
|
const paramsSchema = z.strictObject({
|
||||||
|
orgId: z.string().nonempty(),
|
||||||
|
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||||
|
});
|
||||||
|
|
||||||
|
const querySchema = z.object({
|
||||||
|
limit: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("1000")
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive()),
|
||||||
|
offset: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("0")
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().nonnegative())
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ListBrowserGatewayTargetsResponse = {
|
||||||
|
targets: BrowserGatewayTarget[];
|
||||||
|
total: number;
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.registerPath({
|
||||||
|
method: "get",
|
||||||
|
path: "/org/{orgId}/resource/{resourceId}/browser-gateway-targets",
|
||||||
|
description: "List browser gateway targets for a resource.",
|
||||||
|
tags: [OpenAPITags.Org],
|
||||||
|
request: {
|
||||||
|
params: paramsSchema,
|
||||||
|
query: querySchema
|
||||||
|
},
|
||||||
|
responses: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function listBrowserGatewayTargets(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedParams = paramsSchema.safeParse(req.params);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { orgId, resourceId } = parsedParams.data;
|
||||||
|
|
||||||
|
const parsedQuery = querySchema.safeParse(req.query);
|
||||||
|
if (!parsedQuery.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedQuery.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { limit, offset } = parsedQuery.data;
|
||||||
|
|
||||||
|
const [resource] = await db
|
||||||
|
.select()
|
||||||
|
.from(resources)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(resources.resourceId, resourceId),
|
||||||
|
eq(resources.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!resource) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Resource with ID ${resourceId} not found in organization ${orgId}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targets = await db
|
||||||
|
.select()
|
||||||
|
.from(browserGatewayTarget)
|
||||||
|
.where(eq(browserGatewayTarget.resourceId, resourceId))
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset);
|
||||||
|
|
||||||
|
return response<ListBrowserGatewayTargetsResponse>(res, {
|
||||||
|
data: {
|
||||||
|
targets: targets,
|
||||||
|
total: targets.length,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Browser gateway targets retrieved successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Failed to list browser gateway targets"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
browserGatewayTarget,
|
||||||
|
BrowserGatewayTarget,
|
||||||
|
db,
|
||||||
|
newts,
|
||||||
|
sites
|
||||||
|
} from "@server/db";
|
||||||
|
import { eq, and } from "drizzle-orm";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import { sendBrowserGatewayTargets } from "@server/routers/newt/targets";
|
||||||
|
|
||||||
|
const paramsSchema = z.strictObject({
|
||||||
|
orgId: z.string().nonempty(),
|
||||||
|
browserGatewayTargetId: z
|
||||||
|
.string()
|
||||||
|
.transform(Number)
|
||||||
|
.pipe(z.number().int().positive())
|
||||||
|
});
|
||||||
|
|
||||||
|
const bodySchema = z.strictObject({
|
||||||
|
siteId: z.number().int().positive().optional(),
|
||||||
|
type: z.enum(["ssh", "rdp", "vnc"]).optional(),
|
||||||
|
destination: z.string().nonempty().optional(),
|
||||||
|
destinationPort: z.number().int().min(1).max(65535).optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UpdateBrowserGatewayTargetResponse = BrowserGatewayTarget;
|
||||||
|
|
||||||
|
registry.registerPath({
|
||||||
|
method: "post",
|
||||||
|
path: "/org/{orgId}/browser-gateway-target/{browserGatewayTargetId}",
|
||||||
|
description: "Update a browser gateway target.",
|
||||||
|
tags: [OpenAPITags.Org],
|
||||||
|
request: {
|
||||||
|
params: paramsSchema,
|
||||||
|
body: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: bodySchema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responses: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function updateBrowserGatewayTarget(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedParams = paramsSchema.safeParse(req.params);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { orgId, browserGatewayTargetId } = parsedParams.data;
|
||||||
|
|
||||||
|
const parsedBody = bodySchema.safeParse(req.body);
|
||||||
|
if (!parsedBody.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedBody.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { siteId, type, destination, destinationPort } = parsedBody.data;
|
||||||
|
|
||||||
|
const [existing] = await db
|
||||||
|
.select({ bgt: browserGatewayTarget, site: sites })
|
||||||
|
.from(browserGatewayTarget)
|
||||||
|
.innerJoin(sites, eq(sites.siteId, browserGatewayTarget.siteId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(
|
||||||
|
browserGatewayTarget.browserGatewayTargetId,
|
||||||
|
browserGatewayTargetId
|
||||||
|
),
|
||||||
|
eq(sites.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Browser gateway target with ID ${browserGatewayTargetId} not found`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateValues: Partial<BrowserGatewayTarget> = {};
|
||||||
|
if (siteId !== undefined) updateValues.siteId = siteId;
|
||||||
|
if (type !== undefined) updateValues.type = type;
|
||||||
|
if (destination !== undefined) updateValues.destination = destination;
|
||||||
|
if (destinationPort !== undefined)
|
||||||
|
updateValues.destinationPort = destinationPort;
|
||||||
|
|
||||||
|
const [updated] = await db
|
||||||
|
.update(browserGatewayTarget)
|
||||||
|
.set(updateValues)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
browserGatewayTarget.browserGatewayTargetId,
|
||||||
|
browserGatewayTargetId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
const targetSiteId = siteId ?? existing.bgt.siteId;
|
||||||
|
const [site] = await db
|
||||||
|
.select()
|
||||||
|
.from(sites)
|
||||||
|
.where(eq(sites.siteId, targetSiteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (site && site.type === "newt") {
|
||||||
|
const [newt] = await db
|
||||||
|
.select()
|
||||||
|
.from(newts)
|
||||||
|
.where(eq(newts.siteId, targetSiteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (newt) {
|
||||||
|
await sendBrowserGatewayTargets(
|
||||||
|
newt.newtId,
|
||||||
|
[updated],
|
||||||
|
newt.version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Updated browser gateway target ${browserGatewayTargetId}`);
|
||||||
|
|
||||||
|
return response<UpdateBrowserGatewayTargetResponse>(res, {
|
||||||
|
data: updated,
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Browser gateway target updated successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Failed to update browser gateway target"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ import * as siteProvisioning from "#private/routers/siteProvisioning";
|
|||||||
import * as eventStreamingDestination from "#private/routers/eventStreamingDestination";
|
import * as eventStreamingDestination from "#private/routers/eventStreamingDestination";
|
||||||
import * as alertRule from "#private/routers/alertRule";
|
import * as alertRule from "#private/routers/alertRule";
|
||||||
import * as healthChecks from "#private/routers/healthChecks";
|
import * as healthChecks from "#private/routers/healthChecks";
|
||||||
|
import * as browserGatewayTarget from "#private/routers/browserGatewayTarget";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
@@ -775,3 +776,48 @@ authenticated.get(
|
|||||||
verifyUserHasAction(ActionsEnum.getTarget),
|
verifyUserHasAction(ActionsEnum.getTarget),
|
||||||
healthChecks.getHealthCheckStatusHistory
|
healthChecks.getHealthCheckStatusHistory
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.put(
|
||||||
|
"/org/:orgId/resource/:resourceId/browser-gateway-target",
|
||||||
|
verifyValidLicense,
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyLimits,
|
||||||
|
verifyUserHasAction(ActionsEnum.createBrowserGatewayTarget),
|
||||||
|
logActionAudit(ActionsEnum.createBrowserGatewayTarget),
|
||||||
|
browserGatewayTarget.createBrowserGatewayTarget
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/resource/:resourceId/browser-gateway-targets",
|
||||||
|
verifyValidLicense,
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.listBrowserGatewayTargets),
|
||||||
|
browserGatewayTarget.listBrowserGatewayTargets
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
|
||||||
|
verifyValidLicense,
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.getBrowserGatewayTarget),
|
||||||
|
browserGatewayTarget.getBrowserGatewayTarget
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.post(
|
||||||
|
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
|
||||||
|
verifyValidLicense,
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyLimits,
|
||||||
|
verifyUserHasAction(ActionsEnum.updateBrowserGatewayTarget),
|
||||||
|
logActionAudit(ActionsEnum.updateBrowserGatewayTarget),
|
||||||
|
browserGatewayTarget.updateBrowserGatewayTarget
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.delete(
|
||||||
|
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
|
||||||
|
verifyValidLicense,
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.deleteBrowserGatewayTarget),
|
||||||
|
logActionAudit(ActionsEnum.deleteBrowserGatewayTarget),
|
||||||
|
browserGatewayTarget.deleteBrowserGatewayTarget
|
||||||
|
);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import * as org from "#private/routers/org";
|
|||||||
import * as logs from "#private/routers/auditLogs";
|
import * as logs from "#private/routers/auditLogs";
|
||||||
import * as alertEvents from "#private/routers/alertEvents";
|
import * as alertEvents from "#private/routers/alertEvents";
|
||||||
import * as certificates from "#private/routers/certificates";
|
import * as certificates from "#private/routers/certificates";
|
||||||
|
import * as browserGatewayTarget from "#private/routers/browserGatewayTarget";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
verifyApiKeyHasAction,
|
verifyApiKeyHasAction,
|
||||||
@@ -215,3 +216,43 @@ authenticated.delete(
|
|||||||
logActionAudit(ActionsEnum.removeUserRole),
|
logActionAudit(ActionsEnum.removeUserRole),
|
||||||
user.removeUserRole
|
user.removeUserRole
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.put(
|
||||||
|
"/org/:orgId/resource/:resourceId/browser-gateway-target",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyLimits,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.createBrowserGatewayTarget),
|
||||||
|
logActionAudit(ActionsEnum.createBrowserGatewayTarget),
|
||||||
|
browserGatewayTarget.createBrowserGatewayTarget
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/resource/:resourceId/browser-gateway-targets",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.listBrowserGatewayTargets),
|
||||||
|
browserGatewayTarget.listBrowserGatewayTargets
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.getBrowserGatewayTarget),
|
||||||
|
browserGatewayTarget.getBrowserGatewayTarget
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.post(
|
||||||
|
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyLimits,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.updateBrowserGatewayTarget),
|
||||||
|
logActionAudit(ActionsEnum.updateBrowserGatewayTarget),
|
||||||
|
browserGatewayTarget.updateBrowserGatewayTarget
|
||||||
|
);
|
||||||
|
|
||||||
|
authenticated.delete(
|
||||||
|
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.deleteBrowserGatewayTarget),
|
||||||
|
logActionAudit(ActionsEnum.deleteBrowserGatewayTarget),
|
||||||
|
browserGatewayTarget.deleteBrowserGatewayTarget
|
||||||
|
);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import logger from "@server/logger";
|
|||||||
import { initPeerAddHandshake, updatePeer } from "../olm/peers";
|
import { initPeerAddHandshake, updatePeer } from "../olm/peers";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
|
import { decrypt } from "@server/lib/crypto";
|
||||||
import {
|
import {
|
||||||
formatEndpoint,
|
formatEndpoint,
|
||||||
generateSubnetProxyTargetV2,
|
generateSubnetProxyTargetV2,
|
||||||
@@ -311,12 +312,17 @@ export async function buildTargetConfigurationForNewtClient(
|
|||||||
(target) => target !== null
|
(target) => target !== null
|
||||||
);
|
);
|
||||||
|
|
||||||
const browserGatewayTargets = allBrowserGatewayTargets.map((t) => ({
|
const serverSecret = config.getRawConfig().server.secret!;
|
||||||
|
const browserGatewayTargets = allBrowserGatewayTargets.map((t) => {
|
||||||
|
const decryptAuthToken = decrypt(t.authToken, serverSecret);
|
||||||
|
return {
|
||||||
id: t.browserGatewayTargetId,
|
id: t.browserGatewayTargetId,
|
||||||
type: t.type,
|
type: t.type,
|
||||||
destination: t.destination,
|
destination: t.destination,
|
||||||
destinationPort: t.destinationPort
|
destinationPort: t.destinationPort,
|
||||||
}));
|
authToken: decryptAuthToken
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
validHealthCheckTargets,
|
validHealthCheckTargets,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { BrowserGatewayTarget, Target, TargetHealthCheck } from "@server/db";
|
|||||||
import { sendToClient } from "#dynamic/routers/ws";
|
import { sendToClient } from "#dynamic/routers/ws";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { canCompress } from "@server/lib/clientVersionChecks";
|
import { canCompress } from "@server/lib/clientVersionChecks";
|
||||||
|
import { decrypt } from "@server/lib/crypto";
|
||||||
|
import config from "@server/lib/config";
|
||||||
|
|
||||||
export async function addTargets(
|
export async function addTargets(
|
||||||
newtId: string,
|
newtId: string,
|
||||||
@@ -247,14 +249,21 @@ export async function sendBrowserGatewayTargets(
|
|||||||
) {
|
) {
|
||||||
if (targets.length === 0) return;
|
if (targets.length === 0) return;
|
||||||
|
|
||||||
const payload = targets.map((t) => ({
|
const payload = targets.map((t) => {
|
||||||
|
const decryptAuthToken = decrypt(
|
||||||
|
t.authToken,
|
||||||
|
config.getRawConfig().server.secret!
|
||||||
|
);
|
||||||
|
return {
|
||||||
id: t.browserGatewayTargetId,
|
id: t.browserGatewayTargetId,
|
||||||
resourceId: t.resourceId,
|
resourceId: t.resourceId,
|
||||||
siteId: t.siteId,
|
siteId: t.siteId,
|
||||||
type: t.type,
|
type: t.type,
|
||||||
destination: t.destination,
|
destination: t.destination,
|
||||||
destinationPort: t.destinationPort
|
destinationPort: t.destinationPort,
|
||||||
}));
|
authToken: decryptAuthToken
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
await sendToClient(
|
await sendToClient(
|
||||||
newtId,
|
newtId,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import HttpCode from "@server/types/HttpCode";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
import { decrypt } from "@server/lib/crypto";
|
||||||
|
import config from "@server/lib/config";
|
||||||
|
|
||||||
const getBrowserTargetSchema = z
|
const getBrowserTargetSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -18,6 +20,7 @@ const getBrowserTargetSchema = z
|
|||||||
export type GetBrowserTargetResponse = {
|
export type GetBrowserTargetResponse = {
|
||||||
ip: string;
|
ip: string;
|
||||||
port: number;
|
port: number;
|
||||||
|
authToken: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getBrowserTarget(
|
export async function getBrowserTarget(
|
||||||
@@ -43,7 +46,8 @@ export async function getBrowserTarget(
|
|||||||
const [browserTarget] = await db
|
const [browserTarget] = await db
|
||||||
.select({
|
.select({
|
||||||
destination: browserGatewayTarget.destination,
|
destination: browserGatewayTarget.destination,
|
||||||
destinationPort: browserGatewayTarget.destinationPort
|
destinationPort: browserGatewayTarget.destinationPort,
|
||||||
|
authToken: browserGatewayTarget.authToken
|
||||||
})
|
})
|
||||||
.from(browserGatewayTarget)
|
.from(browserGatewayTarget)
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
@@ -53,6 +57,11 @@ export async function getBrowserTarget(
|
|||||||
.where(eq(resources.fullDomain, fullDomain))
|
.where(eq(resources.fullDomain, fullDomain))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
|
const decryptAuthToken = decrypt(
|
||||||
|
browserTarget.authToken,
|
||||||
|
config.getRawConfig().server.secret!
|
||||||
|
);
|
||||||
|
|
||||||
if (!browserTarget) {
|
if (!browserTarget) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -65,7 +74,8 @@ export async function getBrowserTarget(
|
|||||||
return response<GetBrowserTargetResponse>(res, {
|
return response<GetBrowserTargetResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
ip: browserTarget.destination,
|
ip: browserTarget.destination,
|
||||||
port: browserTarget.destinationPort
|
port: browserTarget.destinationPort,
|
||||||
|
authToken: decryptAuthToken
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ declare module "react" {
|
|||||||
type Target = {
|
type Target = {
|
||||||
ip: string;
|
ip: string;
|
||||||
port: number;
|
port: number;
|
||||||
|
authToken: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormState = {
|
type FormState = {
|
||||||
@@ -219,9 +220,16 @@ export default function RdpClient({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const destination = target ? `${target.ip}:${target.port}` : "";
|
if (!target) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "No target",
|
||||||
|
description: "No connection target available"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Starting RDP session with destination:", destination);
|
const destination = `${target.ip}:${target.port}`;
|
||||||
|
|
||||||
const builder = userInteraction
|
const builder = userInteraction
|
||||||
.configBuilder()
|
.configBuilder()
|
||||||
@@ -232,7 +240,7 @@ export default function RdpClient({
|
|||||||
`${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/gateway/rdp`
|
`${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/gateway/rdp`
|
||||||
)
|
)
|
||||||
.withServerDomain(form.domain)
|
.withServerDomain(form.domain)
|
||||||
.withAuthToken("test-token")
|
.withAuthToken(target.authToken)
|
||||||
.withDesktopSize({
|
.withDesktopSize({
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight
|
height: window.innerHeight
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default async function RdpPage() {
|
|||||||
const host = headersList.get("host") || "";
|
const host = headersList.get("host") || "";
|
||||||
const hostname = host.split(":")[0];
|
const hostname = host.split(":")[0];
|
||||||
|
|
||||||
let target: { ip: string; port: number } | null = null;
|
let target: { ip: string; port: number; authToken: string } | null = null;
|
||||||
let error: string | null = null;
|
let error: string | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
type Target = {
|
type Target = {
|
||||||
ip: string;
|
ip: string;
|
||||||
port: number;
|
port: number;
|
||||||
|
authToken: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormState = {
|
type FormState = {
|
||||||
@@ -125,12 +126,18 @@ export default function SshClient({
|
|||||||
setConnectError(null);
|
setConnectError(null);
|
||||||
setConnecting(true);
|
setConnecting(true);
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
setConnectError("No target specified");
|
||||||
|
setConnecting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const proxyAddress = `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/gateway/ssh`;
|
const proxyAddress = `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/gateway/ssh`;
|
||||||
const url = new URL(proxyAddress);
|
const url = new URL(proxyAddress);
|
||||||
url.searchParams.set("host", target?.ip ?? "");
|
url.searchParams.set("host", target.ip ?? "");
|
||||||
url.searchParams.set("port", String(target?.port ?? 22));
|
url.searchParams.set("port", String(target.port ?? 22));
|
||||||
url.searchParams.set("username", form.username);
|
url.searchParams.set("username", form.username);
|
||||||
url.searchParams.set("authToken", "test-token");
|
url.searchParams.set("authToken", target.authToken ?? "");
|
||||||
|
|
||||||
const ws = new WebSocket(url.toString(), ["ssh"]);
|
const ws = new WebSocket(url.toString(), ["ssh"]);
|
||||||
wsRef.current = ws;
|
wsRef.current = ws;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default async function SshPage() {
|
|||||||
const host = headersList.get("host") || "";
|
const host = headersList.get("host") || "";
|
||||||
const hostname = host.split(":")[0];
|
const hostname = host.split(":")[0];
|
||||||
|
|
||||||
let target: { ip: string; port: number } | null = null;
|
let target: { ip: string; port: number; authToken: string } | null = null;
|
||||||
let error: string | null = null;
|
let error: string | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { toast } from "@app/hooks/useToast";
|
|||||||
type Target = {
|
type Target = {
|
||||||
ip: string;
|
ip: string;
|
||||||
port: number;
|
port: number;
|
||||||
|
authToken: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormState = {
|
type FormState = {
|
||||||
@@ -91,7 +92,7 @@ export default function VncClient({
|
|||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
host: target.ip,
|
host: target.ip,
|
||||||
port: String(target.port),
|
port: String(target.port),
|
||||||
authToken: "test-token"
|
authToken: target.authToken
|
||||||
});
|
});
|
||||||
const wsUrl = `${base}?${params.toString()}`;
|
const wsUrl = `${base}?${params.toString()}`;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default async function VncPage() {
|
|||||||
const host = headersList.get("host") || "";
|
const host = headersList.get("host") || "";
|
||||||
const hostname = host.split(":")[0];
|
const hostname = host.split(":")[0];
|
||||||
|
|
||||||
let target: { ip: string; port: number } | null = null;
|
let target: { ip: string; port: number; authToken: string } | null = null;
|
||||||
let error: string | null = null;
|
let error: string | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user