mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
Add maintence options to blueprints
This commit is contained in:
@@ -2,7 +2,8 @@ import {
|
|||||||
domains,
|
domains,
|
||||||
orgDomains,
|
orgDomains,
|
||||||
Resource,
|
Resource,
|
||||||
resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility,
|
resourceHeaderAuth,
|
||||||
|
resourceHeaderAuthExtendedCompatibility,
|
||||||
resourcePincode,
|
resourcePincode,
|
||||||
resourceRules,
|
resourceRules,
|
||||||
resourceWhitelist,
|
resourceWhitelist,
|
||||||
@@ -16,8 +17,8 @@ import {
|
|||||||
userResources,
|
userResources,
|
||||||
users
|
users
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import {resources, targets, sites} from "@server/db";
|
import { resources, targets, sites } from "@server/db";
|
||||||
import {eq, and, asc, or, ne, count, isNotNull} from "drizzle-orm";
|
import { eq, and, asc, or, ne, count, isNotNull } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
Config,
|
Config,
|
||||||
ConfigSchema,
|
ConfigSchema,
|
||||||
@@ -25,12 +26,13 @@ import {
|
|||||||
TargetData
|
TargetData
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import {createCertificate} from "#dynamic/routers/certificates/createCertificate";
|
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
||||||
import {pickPort} from "@server/routers/target/helpers";
|
import { pickPort } from "@server/routers/target/helpers";
|
||||||
import {resourcePassword} from "@server/db";
|
import { resourcePassword } from "@server/db";
|
||||||
import {hashPassword} from "@server/auth/password";
|
import { hashPassword } from "@server/auth/password";
|
||||||
import {isValidCIDR, isValidIP, isValidUrlGlobPattern} from "../validators";
|
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
|
||||||
import {get} from "http";
|
import { isLicensedOrSubscribed } from "../isLicencedOrSubscribed";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
export type ProxyResourcesResults = {
|
export type ProxyResourcesResults = {
|
||||||
proxyResource: Resource;
|
proxyResource: Resource;
|
||||||
@@ -63,7 +65,7 @@ export async function updateProxyResources(
|
|||||||
if (targetSiteId) {
|
if (targetSiteId) {
|
||||||
// Look up site by niceId
|
// Look up site by niceId
|
||||||
[site] = await trx
|
[site] = await trx
|
||||||
.select({siteId: sites.siteId})
|
.select({ siteId: sites.siteId })
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
@@ -75,7 +77,7 @@ export async function updateProxyResources(
|
|||||||
} else if (siteId) {
|
} else if (siteId) {
|
||||||
// Use the provided siteId directly, but verify it belongs to the org
|
// Use the provided siteId directly, but verify it belongs to the org
|
||||||
[site] = await trx
|
[site] = await trx
|
||||||
.select({siteId: sites.siteId})
|
.select({ siteId: sites.siteId })
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(
|
.where(
|
||||||
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
||||||
@@ -93,7 +95,7 @@ export async function updateProxyResources(
|
|||||||
|
|
||||||
let internalPortToCreate;
|
let internalPortToCreate;
|
||||||
if (!targetData["internal-port"]) {
|
if (!targetData["internal-port"]) {
|
||||||
const {internalPort, targetIps} = await pickPort(
|
const { internalPort, targetIps } = await pickPort(
|
||||||
site.siteId!,
|
site.siteId!,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
@@ -209,6 +211,16 @@ export async function updateProxyResources(
|
|||||||
resource = existingResource;
|
resource = existingResource;
|
||||||
} else {
|
} else {
|
||||||
// Update existing resource
|
// Update existing resource
|
||||||
|
|
||||||
|
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||||
|
if (build == "enterprise" && !isLicensed) {
|
||||||
|
logger.warn(
|
||||||
|
"Server is not licensed! Clearing set maintenance screen values"
|
||||||
|
);
|
||||||
|
// null the maintenance mode fields if not licensed
|
||||||
|
resourceData.maintenance = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
[resource] = await trx
|
[resource] = await trx
|
||||||
.update(resources)
|
.update(resources)
|
||||||
.set({
|
.set({
|
||||||
@@ -228,12 +240,19 @@ export async function updateProxyResources(
|
|||||||
tlsServerName: resourceData["tls-server-name"] || null,
|
tlsServerName: resourceData["tls-server-name"] || null,
|
||||||
emailWhitelistEnabled: resourceData.auth?.[
|
emailWhitelistEnabled: resourceData.auth?.[
|
||||||
"whitelist-users"
|
"whitelist-users"
|
||||||
]
|
]
|
||||||
? resourceData.auth["whitelist-users"].length > 0
|
? resourceData.auth["whitelist-users"].length > 0
|
||||||
: false,
|
: false,
|
||||||
headers: headers || null,
|
headers: headers || null,
|
||||||
applyRules:
|
applyRules:
|
||||||
resourceData.rules && resourceData.rules.length > 0
|
resourceData.rules && resourceData.rules.length > 0,
|
||||||
|
maintenanceModeEnabled:
|
||||||
|
resourceData.maintenance?.enabled,
|
||||||
|
maintenanceModeType: resourceData.maintenance?.type,
|
||||||
|
maintenanceTitle: resourceData.maintenance?.title,
|
||||||
|
maintenanceMessage: resourceData.maintenance?.message,
|
||||||
|
maintenanceEstimatedTime:
|
||||||
|
resourceData.maintenance?.["estimated-time"]
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
eq(resources.resourceId, existingResource.resourceId)
|
eq(resources.resourceId, existingResource.resourceId)
|
||||||
@@ -303,8 +322,13 @@ export async function updateProxyResources(
|
|||||||
const headerAuthPassword =
|
const headerAuthPassword =
|
||||||
resourceData.auth?.["basic-auth"]?.password;
|
resourceData.auth?.["basic-auth"]?.password;
|
||||||
const headerAuthExtendedCompatibility =
|
const headerAuthExtendedCompatibility =
|
||||||
resourceData.auth?.["basic-auth"]?.extendedCompatibility;
|
resourceData.auth?.["basic-auth"]
|
||||||
if (headerAuthUser && headerAuthPassword && headerAuthExtendedCompatibility !== null) {
|
?.extendedCompatibility;
|
||||||
|
if (
|
||||||
|
headerAuthUser &&
|
||||||
|
headerAuthPassword &&
|
||||||
|
headerAuthExtendedCompatibility !== null
|
||||||
|
) {
|
||||||
const headerAuthHash = await hashPassword(
|
const headerAuthHash = await hashPassword(
|
||||||
Buffer.from(
|
Buffer.from(
|
||||||
`${headerAuthUser}:${headerAuthPassword}`
|
`${headerAuthUser}:${headerAuthPassword}`
|
||||||
@@ -315,10 +339,13 @@ export async function updateProxyResources(
|
|||||||
resourceId: existingResource.resourceId,
|
resourceId: existingResource.resourceId,
|
||||||
headerAuthHash
|
headerAuthHash
|
||||||
}),
|
}),
|
||||||
trx.insert(resourceHeaderAuthExtendedCompatibility).values({
|
trx
|
||||||
resourceId: existingResource.resourceId,
|
.insert(resourceHeaderAuthExtendedCompatibility)
|
||||||
extendedCompatibilityIsActivated: headerAuthExtendedCompatibility
|
.values({
|
||||||
})
|
resourceId: existingResource.resourceId,
|
||||||
|
extendedCompatibilityIsActivated:
|
||||||
|
headerAuthExtendedCompatibility
|
||||||
|
})
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,7 +407,7 @@ export async function updateProxyResources(
|
|||||||
if (targetSiteId) {
|
if (targetSiteId) {
|
||||||
// Look up site by niceId
|
// Look up site by niceId
|
||||||
[site] = await trx
|
[site] = await trx
|
||||||
.select({siteId: sites.siteId})
|
.select({ siteId: sites.siteId })
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
@@ -392,7 +419,7 @@ export async function updateProxyResources(
|
|||||||
} else if (siteId) {
|
} else if (siteId) {
|
||||||
// Use the provided siteId directly, but verify it belongs to the org
|
// Use the provided siteId directly, but verify it belongs to the org
|
||||||
[site] = await trx
|
[site] = await trx
|
||||||
.select({siteId: sites.siteId})
|
.select({ siteId: sites.siteId })
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
@@ -437,7 +464,7 @@ export async function updateProxyResources(
|
|||||||
if (checkIfTargetChanged(existingTarget, updatedTarget)) {
|
if (checkIfTargetChanged(existingTarget, updatedTarget)) {
|
||||||
let internalPortToUpdate;
|
let internalPortToUpdate;
|
||||||
if (!targetData["internal-port"]) {
|
if (!targetData["internal-port"]) {
|
||||||
const {internalPort, targetIps} = await pickPort(
|
const { internalPort, targetIps } = await pickPort(
|
||||||
site.siteId!,
|
site.siteId!,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
@@ -622,6 +649,15 @@ export async function updateProxyResources(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||||
|
if (build == "enterprise" && !isLicensed) {
|
||||||
|
logger.warn(
|
||||||
|
"Server is not licensed! Clearing set maintenance screen values"
|
||||||
|
);
|
||||||
|
// null the maintenance mode fields if not licensed
|
||||||
|
resourceData.maintenance = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// Create new resource
|
// Create new resource
|
||||||
const [newResource] = await trx
|
const [newResource] = await trx
|
||||||
.insert(resources)
|
.insert(resources)
|
||||||
@@ -643,7 +679,13 @@ export async function updateProxyResources(
|
|||||||
ssl: resourceSsl,
|
ssl: resourceSsl,
|
||||||
headers: headers || null,
|
headers: headers || null,
|
||||||
applyRules:
|
applyRules:
|
||||||
resourceData.rules && resourceData.rules.length > 0
|
resourceData.rules && resourceData.rules.length > 0,
|
||||||
|
maintenanceModeEnabled: resourceData.maintenance?.enabled,
|
||||||
|
maintenanceModeType: resourceData.maintenance?.type,
|
||||||
|
maintenanceTitle: resourceData.maintenance?.title,
|
||||||
|
maintenanceMessage: resourceData.maintenance?.message,
|
||||||
|
maintenanceEstimatedTime:
|
||||||
|
resourceData.maintenance?.["estimated-time"]
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
@@ -674,9 +716,14 @@ export async function updateProxyResources(
|
|||||||
const headerAuthUser = resourceData.auth?.["basic-auth"]?.user;
|
const headerAuthUser = resourceData.auth?.["basic-auth"]?.user;
|
||||||
const headerAuthPassword =
|
const headerAuthPassword =
|
||||||
resourceData.auth?.["basic-auth"]?.password;
|
resourceData.auth?.["basic-auth"]?.password;
|
||||||
const headerAuthExtendedCompatibility = resourceData.auth?.["basic-auth"]?.extendedCompatibility;
|
const headerAuthExtendedCompatibility =
|
||||||
|
resourceData.auth?.["basic-auth"]?.extendedCompatibility;
|
||||||
|
|
||||||
if (headerAuthUser && headerAuthPassword && headerAuthExtendedCompatibility !== null) {
|
if (
|
||||||
|
headerAuthUser &&
|
||||||
|
headerAuthPassword &&
|
||||||
|
headerAuthExtendedCompatibility !== null
|
||||||
|
) {
|
||||||
const headerAuthHash = await hashPassword(
|
const headerAuthHash = await hashPassword(
|
||||||
Buffer.from(
|
Buffer.from(
|
||||||
`${headerAuthUser}:${headerAuthPassword}`
|
`${headerAuthUser}:${headerAuthPassword}`
|
||||||
@@ -688,10 +735,13 @@ export async function updateProxyResources(
|
|||||||
resourceId: newResource.resourceId,
|
resourceId: newResource.resourceId,
|
||||||
headerAuthHash
|
headerAuthHash
|
||||||
}),
|
}),
|
||||||
trx.insert(resourceHeaderAuthExtendedCompatibility).values({
|
trx
|
||||||
resourceId: newResource.resourceId,
|
.insert(resourceHeaderAuthExtendedCompatibility)
|
||||||
extendedCompatibilityIsActivated: headerAuthExtendedCompatibility
|
.values({
|
||||||
}),
|
resourceId: newResource.resourceId,
|
||||||
|
extendedCompatibilityIsActivated:
|
||||||
|
headerAuthExtendedCompatibility
|
||||||
|
})
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1043,7 +1093,7 @@ async function getDomain(
|
|||||||
trx: Transaction
|
trx: Transaction
|
||||||
) {
|
) {
|
||||||
const [fullDomainExists] = await trx
|
const [fullDomainExists] = await trx
|
||||||
.select({resourceId: resources.resourceId})
|
.select({ resourceId: resources.resourceId })
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { portRangeStringSchema } from "@server/lib/ip";
|
import { portRangeStringSchema } from "@server/lib/ip";
|
||||||
|
import { MaintenanceSchema } from "#dynamic/lib/blueprints/types";
|
||||||
|
|
||||||
export const SiteSchema = z.object({
|
export const SiteSchema = z.object({
|
||||||
name: z.string().min(1).max(100),
|
name: z.string().min(1).max(100),
|
||||||
@@ -156,7 +157,8 @@ export const ResourceSchema = z
|
|||||||
"host-header": z.string().optional(),
|
"host-header": z.string().optional(),
|
||||||
"tls-server-name": z.string().optional(),
|
"tls-server-name": z.string().optional(),
|
||||||
headers: z.array(HeaderSchema).optional(),
|
headers: z.array(HeaderSchema).optional(),
|
||||||
rules: z.array(RuleSchema).optional()
|
rules: z.array(RuleSchema).optional(),
|
||||||
|
maintenance: MaintenanceSchema.optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(resource) => {
|
(resource) => {
|
||||||
|
|||||||
9
server/private/lib/blueprints/types.ts
Normal file
9
server/private/lib/blueprints/types.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const MaintenanceSchema = z.object({
|
||||||
|
enabled: z.boolean().optional(),
|
||||||
|
type: z.enum(["forced", "automatic"]).optional(),
|
||||||
|
title: z.string().max(255).nullable().optional(),
|
||||||
|
message: z.string().max(2000).nullable().optional(),
|
||||||
|
"estimated-time": z.string().max(100).nullable().optional()
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user