mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-23 21:36:37 +00:00
Merge branch 'dev' into refactor/show-product-updates-conditionnally
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { join } from "path";
|
||||
import { readFileSync } from "fs";
|
||||
import { db, resources, siteResources } from "@server/db";
|
||||
import { randomInt } from "crypto";
|
||||
import { exitNodes, sites } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { __DIRNAME } from "@server/lib/consts";
|
||||
@@ -111,10 +112,10 @@ export async function getUniqueExitNodeEndpointName(): Promise<string> {
|
||||
export function generateName(): string {
|
||||
const name = (
|
||||
names.descriptors[
|
||||
Math.floor(Math.random() * names.descriptors.length)
|
||||
randomInt(names.descriptors.length)
|
||||
] +
|
||||
"-" +
|
||||
names.animals[Math.floor(Math.random() * names.animals.length)]
|
||||
names.animals[randomInt(names.animals.length)]
|
||||
)
|
||||
.toLowerCase()
|
||||
.replace(/\s/g, "-");
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db, olms } from "@server/db";
|
||||
import { db, Olm, olms } from "@server/db";
|
||||
import { clients } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -23,7 +23,7 @@ import { eq, and } from "drizzle-orm";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { hashPassword } from "@server/auth/password";
|
||||
import { disconnectClient, sendToClient } from "#dynamic/routers/ws";
|
||||
import { disconnectClient, sendToClient } from "#private/routers/ws";
|
||||
|
||||
const reGenerateSecretParamsSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
@@ -31,29 +31,12 @@ const reGenerateSecretParamsSchema = z.strictObject({
|
||||
|
||||
const reGenerateSecretBodySchema = z.strictObject({
|
||||
// olmId: z.string().min(1).optional(),
|
||||
secret: z.string().min(1)
|
||||
secret: z.string().min(1),
|
||||
disconnect: z.boolean().optional().default(true)
|
||||
});
|
||||
|
||||
export type ReGenerateSecretBody = z.infer<typeof reGenerateSecretBodySchema>;
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
path: "/re-key/{clientId}/regenerate-client-secret",
|
||||
description: "Regenerate a client's OLM credentials by its client ID.",
|
||||
tags: [OpenAPITags.Client],
|
||||
request: {
|
||||
params: reGenerateSecretParamsSchema,
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: reGenerateSecretBodySchema
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
});
|
||||
|
||||
export async function reGenerateClientSecret(
|
||||
req: Request,
|
||||
res: Response,
|
||||
@@ -70,7 +53,7 @@ export async function reGenerateClientSecret(
|
||||
);
|
||||
}
|
||||
|
||||
const { secret } = parsedBody.data;
|
||||
const { secret, disconnect } = parsedBody.data;
|
||||
|
||||
const parsedParams = reGenerateSecretParamsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
@@ -132,21 +115,26 @@ export async function reGenerateClientSecret(
|
||||
})
|
||||
.where(eq(olms.olmId, existingOlms[0].olmId));
|
||||
|
||||
const payload = {
|
||||
type: `olm/terminate`,
|
||||
data: {}
|
||||
};
|
||||
// Don't await this to prevent blocking the response
|
||||
sendToClient(existingOlms[0].olmId, payload).catch((error) => {
|
||||
logger.error("Failed to send termination message to olm:", error);
|
||||
});
|
||||
// Only disconnect if explicitly requested
|
||||
if (disconnect) {
|
||||
const payload = {
|
||||
type: `olm/terminate`,
|
||||
data: {}
|
||||
};
|
||||
// Don't await this to prevent blocking the response
|
||||
sendToClient(existingOlms[0].olmId, payload).catch((error) => {
|
||||
logger.error("Failed to send termination message to olm:", error);
|
||||
});
|
||||
|
||||
disconnectClient(existingOlms[0].olmId).catch((error) => {
|
||||
logger.error("Failed to disconnect olm after re-key:", error);
|
||||
});
|
||||
disconnectClient(existingOlms[0].olmId).catch((error) => {
|
||||
logger.error("Failed to disconnect olm after re-key:", error);
|
||||
});
|
||||
}
|
||||
|
||||
return response(res, {
|
||||
data: existingOlms,
|
||||
data: {
|
||||
olmId: existingOlms[0].olmId,
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Credentials regenerated successfully",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { db, exitNodes, exitNodeOrgs, ExitNode, ExitNodeOrg } from "@server/db";
|
||||
import { db, exitNodes, exitNodeOrgs, ExitNode, ExitNodeOrg, RemoteExitNode } from "@server/db";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { z } from "zod";
|
||||
import { remoteExitNodes } from "@server/db";
|
||||
@@ -22,9 +22,8 @@ import { fromError } from "zod-validation-error";
|
||||
import { hashPassword } from "@server/auth/password";
|
||||
import logger from "@server/logger";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { UpdateRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { disconnectClient } from "@server/routers/ws";
|
||||
import { disconnectClient, sendToClient } from "#private/routers/ws";
|
||||
|
||||
export const paramsSchema = z.object({
|
||||
orgId: z.string()
|
||||
@@ -32,25 +31,8 @@ export const paramsSchema = z.object({
|
||||
|
||||
const bodySchema = z.strictObject({
|
||||
remoteExitNodeId: z.string().length(15),
|
||||
secret: z.string().length(48)
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
path: "/re-key/{orgId}/regenerate-secret",
|
||||
description: "Regenerate a exit node credentials by its org ID.",
|
||||
tags: [OpenAPITags.Org],
|
||||
request: {
|
||||
params: paramsSchema,
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: bodySchema
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
secret: z.string().length(48),
|
||||
disconnect: z.boolean().optional().default(true)
|
||||
});
|
||||
|
||||
export async function reGenerateExitNodeSecret(
|
||||
@@ -79,7 +61,7 @@ export async function reGenerateExitNodeSecret(
|
||||
);
|
||||
}
|
||||
|
||||
const { remoteExitNodeId, secret } = parsedBody.data;
|
||||
const { remoteExitNodeId, secret, disconnect } = parsedBody.data;
|
||||
|
||||
const [existingRemoteExitNode] = await db
|
||||
.select()
|
||||
@@ -102,17 +84,34 @@ export async function reGenerateExitNodeSecret(
|
||||
.set({ secretHash })
|
||||
.where(eq(remoteExitNodes.remoteExitNodeId, remoteExitNodeId));
|
||||
|
||||
disconnectClient(existingRemoteExitNode.remoteExitNodeId).catch(
|
||||
(error) => {
|
||||
logger.error("Failed to disconnect newt after re-key:", error);
|
||||
}
|
||||
);
|
||||
// Only disconnect if explicitly requested
|
||||
if (disconnect) {
|
||||
const payload = {
|
||||
type: `remoteExitNode/terminate`,
|
||||
data: {}
|
||||
};
|
||||
// Don't await this to prevent blocking the response
|
||||
sendToClient(existingRemoteExitNode.remoteExitNodeId, payload).catch(
|
||||
(error) => {
|
||||
logger.error(
|
||||
"Failed to send termination message to remote exit node:",
|
||||
error
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return response<UpdateRemoteExitNodeResponse>(res, {
|
||||
data: {
|
||||
remoteExitNodeId,
|
||||
secret
|
||||
},
|
||||
disconnectClient(existingRemoteExitNode.remoteExitNodeId).catch(
|
||||
(error) => {
|
||||
logger.error(
|
||||
"Failed to disconnect remote exit node after re-key:",
|
||||
error
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return response(res, {
|
||||
data: null,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Remote Exit Node secret updated successfully",
|
||||
|
||||
@@ -24,7 +24,7 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { hashPassword } from "@server/auth/password";
|
||||
import { addPeer, deletePeer } from "@server/routers/gerbil/peers";
|
||||
import { getAllowedIps } from "@server/routers/target/helpers";
|
||||
import { disconnectClient, sendToClient } from "#dynamic/routers/ws";
|
||||
import { disconnectClient, sendToClient } from "#private/routers/ws";
|
||||
|
||||
const updateSiteParamsSchema = z.strictObject({
|
||||
siteId: z.string().transform(Number).pipe(z.int().positive())
|
||||
@@ -33,26 +33,8 @@ const updateSiteParamsSchema = z.strictObject({
|
||||
const updateSiteBodySchema = z.strictObject({
|
||||
type: z.enum(["newt", "wireguard"]),
|
||||
secret: z.string().min(1).max(255).optional(),
|
||||
pubKey: z.string().optional()
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
path: "/re-key/{siteId}/regenerate-site-secret",
|
||||
description:
|
||||
"Regenerate a site's Newt or WireGuard credentials by its site ID.",
|
||||
tags: [OpenAPITags.Site],
|
||||
request: {
|
||||
params: updateSiteParamsSchema,
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: updateSiteBodySchema
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {}
|
||||
pubKey: z.string().optional(),
|
||||
disconnect: z.boolean().optional().default(true)
|
||||
});
|
||||
|
||||
export async function reGenerateSiteSecret(
|
||||
@@ -82,7 +64,7 @@ export async function reGenerateSiteSecret(
|
||||
}
|
||||
|
||||
const { siteId } = parsedParams.data;
|
||||
const { type, pubKey, secret } = parsedBody.data;
|
||||
const { type, pubKey, secret, disconnect } = parsedBody.data;
|
||||
|
||||
let existingNewt: Newt | null = null;
|
||||
if (type === "newt") {
|
||||
@@ -131,21 +113,24 @@ export async function reGenerateSiteSecret(
|
||||
})
|
||||
.where(eq(newts.newtId, existingNewts[0].newtId));
|
||||
|
||||
const payload = {
|
||||
type: `newt/wg/terminate`,
|
||||
data: {}
|
||||
};
|
||||
// Don't await this to prevent blocking the response
|
||||
sendToClient(existingNewts[0].newtId, payload).catch((error) => {
|
||||
logger.error(
|
||||
"Failed to send termination message to newt:",
|
||||
error
|
||||
);
|
||||
});
|
||||
// Only disconnect if explicitly requested
|
||||
if (disconnect) {
|
||||
const payload = {
|
||||
type: `newt/wg/terminate`,
|
||||
data: {}
|
||||
};
|
||||
// Don't await this to prevent blocking the response
|
||||
sendToClient(existingNewts[0].newtId, payload).catch((error) => {
|
||||
logger.error(
|
||||
"Failed to send termination message to newt:",
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
disconnectClient(existingNewts[0].newtId).catch((error) => {
|
||||
logger.error("Failed to disconnect newt after re-key:", error);
|
||||
});
|
||||
disconnectClient(existingNewts[0].newtId).catch((error) => {
|
||||
logger.error("Failed to disconnect newt after re-key:", error);
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`Regenerated Newt credentials for site ${siteId}`);
|
||||
} else if (type === "wireguard") {
|
||||
@@ -214,7 +199,9 @@ export async function reGenerateSiteSecret(
|
||||
}
|
||||
|
||||
return response(res, {
|
||||
data: existingNewt,
|
||||
data: {
|
||||
newtId: existingNewt ? existingNewt.newtId : undefined
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Credentials regenerated successfully",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { clients, clientSitesAssociationsCache } from "@server/db";
|
||||
import { db, olms } from "@server/db";
|
||||
import { clients } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -12,8 +12,8 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const getClientSchema = z.strictObject({
|
||||
clientId: z.string().transform(stoi).pipe(z.int().positive())
|
||||
});
|
||||
clientId: z.string().transform(stoi).pipe(z.int().positive())
|
||||
});
|
||||
|
||||
async function query(clientId: number) {
|
||||
// Get the client
|
||||
@@ -21,26 +21,20 @@ async function query(clientId: number) {
|
||||
.select()
|
||||
.from(clients)
|
||||
.where(and(eq(clients.clientId, clientId)))
|
||||
.leftJoin(olms, eq(clients.olmId, olms.olmId))
|
||||
.limit(1);
|
||||
|
||||
if (!client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the siteIds associated with this client
|
||||
const sites = await db
|
||||
.select({ siteId: clientSitesAssociationsCache.siteId })
|
||||
.from(clientSitesAssociationsCache)
|
||||
.where(eq(clientSitesAssociationsCache.clientId, clientId));
|
||||
|
||||
// Add the siteIds to the client object
|
||||
return {
|
||||
...client,
|
||||
siteIds: sites.map((site) => site.siteId)
|
||||
};
|
||||
return client;
|
||||
}
|
||||
|
||||
export type GetClientResponse = NonNullable<Awaited<ReturnType<typeof query>>>;
|
||||
export type GetClientResponse = NonNullable<
|
||||
Awaited<ReturnType<typeof query>>
|
||||
>["clients"] & {
|
||||
olmId: string | null;
|
||||
};
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
@@ -82,8 +76,13 @@ export async function getClient(
|
||||
);
|
||||
}
|
||||
|
||||
const data: GetClientResponse = {
|
||||
...client.clients,
|
||||
olmId: client.olms ? client.olms.olmId : null
|
||||
};
|
||||
|
||||
return response<GetClientResponse>(res, {
|
||||
data: client,
|
||||
data,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Client retrieved successfully",
|
||||
|
||||
@@ -6,11 +6,6 @@ export type CreateRemoteExitNodeResponse = {
|
||||
secret: string;
|
||||
};
|
||||
|
||||
export type UpdateRemoteExitNodeResponse = {
|
||||
remoteExitNodeId: string;
|
||||
secret: string;
|
||||
}
|
||||
|
||||
export type PickRemoteExitNodeDefaultsResponse = {
|
||||
remoteExitNodeId: string;
|
||||
secret: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { db, newts } from "@server/db";
|
||||
import { sites } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
@@ -12,15 +12,15 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const getSiteSchema = z.strictObject({
|
||||
siteId: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform(stoi)
|
||||
.pipe(z.int().positive().optional())
|
||||
.optional(),
|
||||
niceId: z.string().optional(),
|
||||
orgId: z.string().optional()
|
||||
});
|
||||
siteId: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform(stoi)
|
||||
.pipe(z.int().positive().optional())
|
||||
.optional(),
|
||||
niceId: z.string().optional(),
|
||||
orgId: z.string().optional()
|
||||
});
|
||||
|
||||
async function query(siteId?: number, niceId?: string, orgId?: string) {
|
||||
if (siteId) {
|
||||
@@ -28,6 +28,7 @@ async function query(siteId?: number, niceId?: string, orgId?: string) {
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.siteId, siteId))
|
||||
.leftJoin(newts, eq(sites.siteId, newts.siteId))
|
||||
.limit(1);
|
||||
return res;
|
||||
} else if (niceId && orgId) {
|
||||
@@ -35,12 +36,15 @@ async function query(siteId?: number, niceId?: string, orgId?: string) {
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(and(eq(sites.niceId, niceId), eq(sites.orgId, orgId)))
|
||||
.leftJoin(newts, eq(sites.siteId, newts.siteId))
|
||||
.limit(1);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export type GetSiteResponse = NonNullable<Awaited<ReturnType<typeof query>>>;
|
||||
export type GetSiteResponse = NonNullable<
|
||||
Awaited<ReturnType<typeof query>>
|
||||
>["sites"] & { newtId: string | null };
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
@@ -94,8 +98,13 @@ export async function getSite(
|
||||
return next(createHttpError(HttpCode.NOT_FOUND, "Site not found"));
|
||||
}
|
||||
|
||||
const data: GetSiteResponse = {
|
||||
...site.sites,
|
||||
newtId: site.newt ? site.newt.newtId : null
|
||||
};
|
||||
|
||||
return response<GetSiteResponse>(res, {
|
||||
data: site,
|
||||
data,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Site retrieved successfully",
|
||||
|
||||
@@ -203,6 +203,12 @@ export async function updateTarget(
|
||||
hcHeaders = JSON.stringify(parsedBody.data.hcHeaders);
|
||||
}
|
||||
|
||||
// When health check is disabled, reset hcHealth to "unknown"
|
||||
// to prevent previously unhealthy targets from being excluded
|
||||
const hcHealthValue = (parsedBody.data.hcEnabled === false || parsedBody.data.hcEnabled === null)
|
||||
? "unknown"
|
||||
: undefined;
|
||||
|
||||
const [updatedHc] = await db
|
||||
.update(targetHealthCheck)
|
||||
.set({
|
||||
@@ -220,6 +226,7 @@ export async function updateTarget(
|
||||
hcMethod: parsedBody.data.hcMethod,
|
||||
hcStatus: parsedBody.data.hcStatus,
|
||||
hcTlsServerName: parsedBody.data.hcTlsServerName,
|
||||
...(hcHealthValue !== undefined && { hcHealth: hcHealthValue })
|
||||
})
|
||||
.where(eq(targetHealthCheck.targetId, targetId))
|
||||
.returning();
|
||||
|
||||
Reference in New Issue
Block a user