mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-16 01:46:38 +00:00
Merge branch 'dev' into clients-user
This commit is contained in:
@@ -19,6 +19,7 @@ export enum ActionsEnum {
|
||||
getSite = "getSite",
|
||||
listSites = "listSites",
|
||||
updateSite = "updateSite",
|
||||
reGenerateSecret = "reGenerateSecret",
|
||||
createResource = "createResource",
|
||||
deleteResource = "deleteResource",
|
||||
getResource = "getResource",
|
||||
|
||||
@@ -7,20 +7,20 @@ export const SiteSchema = z.object({
|
||||
|
||||
export const TargetHealthCheckSchema = z.object({
|
||||
hostname: z.string(),
|
||||
port: z.number().int().min(1).max(65535),
|
||||
port: z.int().min(1).max(65535),
|
||||
enabled: z.boolean().optional().default(true),
|
||||
path: z.string().optional(),
|
||||
scheme: z.string().optional(),
|
||||
mode: z.string().default("http"),
|
||||
interval: z.number().int().default(30),
|
||||
"unhealthy-interval": z.number().int().default(30),
|
||||
unhealthyInterval: z.number().int().optional(), // deprecated alias
|
||||
timeout: z.number().int().default(5),
|
||||
interval: z.int().default(30),
|
||||
"unhealthy-interval": z.int().default(30),
|
||||
unhealthyInterval: z.int().optional(), // deprecated alias
|
||||
timeout: z.int().default(5),
|
||||
headers: z.array(z.object({ name: z.string(), value: z.string() })).nullable().optional().default(null),
|
||||
"follow-redirects": z.boolean().default(true),
|
||||
followRedirects: z.boolean().optional(), // deprecated alias
|
||||
method: z.string().default("GET"),
|
||||
status: z.number().int().optional()
|
||||
status: z.int().optional()
|
||||
});
|
||||
|
||||
// Schema for individual target within a resource
|
||||
@@ -28,16 +28,16 @@ export const TargetSchema = z.object({
|
||||
site: z.string().optional(),
|
||||
method: z.enum(["http", "https", "h2c"]).optional(),
|
||||
hostname: z.string(),
|
||||
port: z.number().int().min(1).max(65535),
|
||||
port: z.int().min(1).max(65535),
|
||||
enabled: z.boolean().optional().default(true),
|
||||
"internal-port": z.number().int().min(1).max(65535).optional(),
|
||||
"internal-port": z.int().min(1).max(65535).optional(),
|
||||
path: z.string().optional(),
|
||||
"path-match": z.enum(["exact", "prefix", "regex"]).optional().nullable(),
|
||||
healthcheck: TargetHealthCheckSchema.optional(),
|
||||
rewritePath: z.string().optional(), // deprecated alias
|
||||
"rewrite-path": z.string().optional(),
|
||||
"rewrite-match": z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable(),
|
||||
priority: z.number().int().min(1).max(1000).optional().default(100)
|
||||
priority: z.int().min(1).max(1000).optional().default(100)
|
||||
});
|
||||
export type TargetData = z.infer<typeof TargetSchema>;
|
||||
|
||||
@@ -55,10 +55,10 @@ export const AuthSchema = z.object({
|
||||
.optional()
|
||||
.default([])
|
||||
.refine((roles) => !roles.includes("Admin"), {
|
||||
message: "Admin role cannot be included in sso-roles"
|
||||
error: "Admin role cannot be included in sso-roles"
|
||||
}),
|
||||
"sso-users": z.array(z.string().email()).optional().default([]),
|
||||
"whitelist-users": z.array(z.string().email()).optional().default([]),
|
||||
"sso-users": z.array(z.email()).optional().default([]),
|
||||
"whitelist-users": z.array(z.email()).optional().default([]),
|
||||
});
|
||||
|
||||
export const RuleSchema = z.object({
|
||||
@@ -79,7 +79,7 @@ export const ResourceSchema = z
|
||||
protocol: z.enum(["http", "tcp", "udp"]).optional(),
|
||||
ssl: z.boolean().optional(),
|
||||
"full-domain": z.string().optional(),
|
||||
"proxy-port": z.number().int().min(1).max(65535).optional(),
|
||||
"proxy-port": z.int().min(1).max(65535).optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
targets: z.array(TargetSchema.nullable()).optional().default([]),
|
||||
auth: AuthSchema.optional(),
|
||||
@@ -100,9 +100,8 @@ export const ResourceSchema = z
|
||||
);
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Resource must either be targets-only (only 'targets' field) or have both 'name' and 'protocol' fields at a minimum",
|
||||
path: ["name", "protocol"]
|
||||
path: ["name", "protocol"],
|
||||
error: "Resource must either be targets-only (only 'targets' field) or have both 'name' and 'protocol' fields at a minimum"
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
@@ -117,6 +116,20 @@ export const ResourceSchema = z
|
||||
(target) => target == null || target.method !== undefined
|
||||
);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
path: ["targets"],
|
||||
error: "When protocol is 'http', all targets must have a 'method' field"
|
||||
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(resource) => {
|
||||
if (isTargetsOnlyResource(resource)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If protocol is tcp or udp, no target should have method field
|
||||
if (resource.protocol === "tcp" || resource.protocol === "udp") {
|
||||
return resource.targets.every(
|
||||
@@ -125,19 +138,9 @@ export const ResourceSchema = z
|
||||
}
|
||||
return true;
|
||||
},
|
||||
(resource) => {
|
||||
if (resource.protocol === "http") {
|
||||
return {
|
||||
message:
|
||||
"When protocol is 'http', all targets must have a 'method' field",
|
||||
path: ["targets"]
|
||||
};
|
||||
}
|
||||
return {
|
||||
message:
|
||||
"When protocol is 'tcp' or 'udp', targets must not have a 'method' field",
|
||||
path: ["targets"]
|
||||
};
|
||||
{
|
||||
path: ["targets"],
|
||||
error: "When protocol is 'tcp' or 'udp', targets must not have a 'method' field"
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
@@ -156,9 +159,8 @@ export const ResourceSchema = z
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"When protocol is 'http', a 'full-domain' must be provided",
|
||||
path: ["full-domain"]
|
||||
path: ["full-domain"],
|
||||
error: "When protocol is 'http', a 'full-domain' must be provided"
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
@@ -174,9 +176,8 @@ export const ResourceSchema = z
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"When protocol is 'tcp' or 'udp', 'proxy-port' must be provided",
|
||||
path: ["proxy-port", "exit-node"]
|
||||
path: ["proxy-port", "exit-node"],
|
||||
error: "When protocol is 'tcp' or 'udp', 'proxy-port' must be provided"
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
@@ -193,9 +194,8 @@ export const ResourceSchema = z
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"When protocol is 'tcp' or 'udp', 'auth' must not be provided",
|
||||
path: ["auth"]
|
||||
path: ["auth"],
|
||||
error: "When protocol is 'tcp' or 'udp', 'auth' must not be provided"
|
||||
}
|
||||
);
|
||||
|
||||
@@ -216,36 +216,12 @@ export const ClientResourceSchema = z.object({
|
||||
// Schema for the entire configuration object
|
||||
export const ConfigSchema = z
|
||||
.object({
|
||||
"proxy-resources": z.record(z.string(), ResourceSchema).optional().default({}),
|
||||
"client-resources": z.record(z.string(), ClientResourceSchema).optional().default({}),
|
||||
sites: z.record(z.string(), SiteSchema).optional().default({})
|
||||
"proxy-resources": z.record(z.string(), ResourceSchema).optional().prefault({}),
|
||||
"client-resources": z.record(z.string(), ClientResourceSchema).optional().prefault({}),
|
||||
sites: z.record(z.string(), SiteSchema).optional().prefault({})
|
||||
})
|
||||
.refine(
|
||||
// Enforce the full-domain uniqueness across resources in the same stack
|
||||
(config) => {
|
||||
// Extract all full-domain values with their resource keys
|
||||
const fullDomainMap = new Map<string, string[]>();
|
||||
|
||||
Object.entries(config["proxy-resources"]).forEach(
|
||||
([resourceKey, resource]) => {
|
||||
const fullDomain = resource["full-domain"];
|
||||
if (fullDomain) {
|
||||
// Only process if full-domain is defined
|
||||
if (!fullDomainMap.has(fullDomain)) {
|
||||
fullDomainMap.set(fullDomain, []);
|
||||
}
|
||||
fullDomainMap.get(fullDomain)!.push(resourceKey);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Find duplicates
|
||||
const duplicates = Array.from(fullDomainMap.entries()).filter(
|
||||
([_, resourceKeys]) => resourceKeys.length > 1
|
||||
);
|
||||
|
||||
return duplicates.length === 0;
|
||||
},
|
||||
(config) => {
|
||||
// Extract duplicates for error message
|
||||
const fullDomainMap = new Map<string, string[]>();
|
||||
@@ -271,38 +247,16 @@ export const ConfigSchema = z
|
||||
)
|
||||
.join("; ");
|
||||
|
||||
return {
|
||||
message: `Duplicate 'full-domain' values found: ${duplicates}`,
|
||||
path: ["resources"]
|
||||
};
|
||||
if (duplicates.length !== 0) {
|
||||
return {
|
||||
path: ["resources"],
|
||||
error: `Duplicate 'full-domain' values found: ${duplicates}`
|
||||
};
|
||||
}
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
// Enforce proxy-port uniqueness within proxy-resources per protocol
|
||||
(config) => {
|
||||
const protocolPortMap = new Map<string, string[]>();
|
||||
|
||||
Object.entries(config["proxy-resources"]).forEach(
|
||||
([resourceKey, resource]) => {
|
||||
const proxyPort = resource["proxy-port"];
|
||||
const protocol = resource.protocol;
|
||||
if (proxyPort !== undefined && protocol !== undefined) {
|
||||
const key = `${protocol}:${proxyPort}`;
|
||||
if (!protocolPortMap.has(key)) {
|
||||
protocolPortMap.set(key, []);
|
||||
}
|
||||
protocolPortMap.get(key)!.push(resourceKey);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Find duplicates
|
||||
const duplicates = Array.from(protocolPortMap.entries()).filter(
|
||||
([_, resourceKeys]) => resourceKeys.length > 1
|
||||
);
|
||||
|
||||
return duplicates.length === 0;
|
||||
},
|
||||
(config) => {
|
||||
// Extract duplicates for error message
|
||||
const protocolPortMap = new Map<string, string[]>();
|
||||
@@ -331,36 +285,16 @@ export const ConfigSchema = z
|
||||
)
|
||||
.join("; ");
|
||||
|
||||
return {
|
||||
message: `Duplicate 'proxy-port' values found in proxy-resources: ${duplicates}`,
|
||||
path: ["proxy-resources"]
|
||||
};
|
||||
if (duplicates.length !== 0) {
|
||||
return {
|
||||
path: ["proxy-resources"],
|
||||
error: `Duplicate 'proxy-port' values found in proxy-resources: ${duplicates}`
|
||||
};
|
||||
}
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
// Enforce proxy-port uniqueness within client-resources
|
||||
(config) => {
|
||||
const proxyPortMap = new Map<number, string[]>();
|
||||
|
||||
Object.entries(config["client-resources"]).forEach(
|
||||
([resourceKey, resource]) => {
|
||||
const proxyPort = resource["proxy-port"];
|
||||
if (proxyPort !== undefined) {
|
||||
if (!proxyPortMap.has(proxyPort)) {
|
||||
proxyPortMap.set(proxyPort, []);
|
||||
}
|
||||
proxyPortMap.get(proxyPort)!.push(resourceKey);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Find duplicates
|
||||
const duplicates = Array.from(proxyPortMap.entries()).filter(
|
||||
([_, resourceKeys]) => resourceKeys.length > 1
|
||||
);
|
||||
|
||||
return duplicates.length === 0;
|
||||
},
|
||||
(config) => {
|
||||
// Extract duplicates for error message
|
||||
const proxyPortMap = new Map<number, string[]>();
|
||||
@@ -385,10 +319,12 @@ export const ConfigSchema = z
|
||||
)
|
||||
.join("; ");
|
||||
|
||||
return {
|
||||
message: `Duplicate 'proxy-port' values found in client-resources: ${duplicates}`,
|
||||
path: ["client-resources"]
|
||||
};
|
||||
if (duplicates.length !== 0) {
|
||||
return {
|
||||
path: ["client-resources"],
|
||||
error: `Duplicate 'proxy-port' values found in client-resources: ${duplicates}`
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -89,6 +89,16 @@ export class Config {
|
||||
? "true"
|
||||
: "false";
|
||||
|
||||
process.env.PRODUCT_UPDATES_NOTIFICATION_ENABLED = parsedConfig.app
|
||||
.notifications.product_updates
|
||||
? "true"
|
||||
: "false";
|
||||
|
||||
process.env.NEW_RELEASES_NOTIFICATION_ENABLED = parsedConfig.app
|
||||
.notifications.new_releases
|
||||
? "true"
|
||||
: "false";
|
||||
|
||||
if (parsedConfig.server.maxmind_db_path) {
|
||||
process.env.MAXMIND_DB_PATH = parsedConfig.server.maxmind_db_path;
|
||||
}
|
||||
@@ -158,7 +168,7 @@ export class Config {
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://api.fossorial.io/api/v1/license/validate",
|
||||
`https://api.fossorial.io/api/v1/license/validate`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
||||
@@ -308,7 +308,7 @@ export function generateRemoteSubnetsStr(allSiteResources: SiteResource[]) {
|
||||
if (sr.mode === "cidr") return true;
|
||||
if (sr.mode === "host") {
|
||||
// check if its a valid IP using zod
|
||||
const ipSchema = z.string().ip();
|
||||
const ipSchema = z.union([z.ipv4(), z.ipv6()]);
|
||||
const parseResult = ipSchema.safeParse(sr.destination);
|
||||
return parseResult.success;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,8 @@ export const configSchema = z
|
||||
.object({
|
||||
app: z
|
||||
.object({
|
||||
dashboard_url: z
|
||||
.string()
|
||||
.url()
|
||||
.pipe(z.string().url())
|
||||
dashboard_url: z.url()
|
||||
.pipe(z.url())
|
||||
.transform((url) => url.toLowerCase())
|
||||
.optional(),
|
||||
log_level: z
|
||||
@@ -31,7 +29,14 @@ export const configSchema = z
|
||||
anonymous_usage: z.boolean().optional().default(true)
|
||||
})
|
||||
.optional()
|
||||
.default({})
|
||||
.prefault({}),
|
||||
notifications: z
|
||||
.object({
|
||||
product_updates: z.boolean().optional().default(true),
|
||||
new_releases: z.boolean().optional().default(true)
|
||||
})
|
||||
.optional()
|
||||
.prefault({})
|
||||
})
|
||||
.optional()
|
||||
.default({
|
||||
@@ -40,6 +45,10 @@ export const configSchema = z
|
||||
log_failed_attempts: false,
|
||||
telemetry: {
|
||||
anonymous_usage: true
|
||||
},
|
||||
notifications: {
|
||||
product_updates: true,
|
||||
new_releases: true
|
||||
}
|
||||
}),
|
||||
domains: z
|
||||
@@ -96,7 +105,7 @@ export const configSchema = z
|
||||
token: z.string().optional().default("P-Access-Token")
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
.prefault({}),
|
||||
resource_session_request_param: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -121,7 +130,7 @@ export const configSchema = z
|
||||
credentials: z.boolean().optional()
|
||||
})
|
||||
.optional(),
|
||||
trust_proxy: z.number().int().gte(0).optional().default(1),
|
||||
trust_proxy: z.int().gte(0).optional().default(1),
|
||||
secret: z.string().pipe(z.string().min(8)).optional(),
|
||||
maxmind_db_path: z.string().optional()
|
||||
})
|
||||
@@ -178,7 +187,7 @@ export const configSchema = z
|
||||
.default(5000)
|
||||
})
|
||||
.optional()
|
||||
.default({})
|
||||
.prefault({})
|
||||
})
|
||||
.optional(),
|
||||
traefik: z
|
||||
@@ -205,10 +214,13 @@ export const configSchema = z
|
||||
.default(["newt", "wireguard", "local"]),
|
||||
allow_raw_resources: z.boolean().optional().default(true),
|
||||
file_mode: z.boolean().optional().default(false),
|
||||
pp_transport_prefix: z.string().optional().default("pp-transport-v")
|
||||
pp_transport_prefix: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("pp-transport-v")
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
.prefault({}),
|
||||
gerbil: z
|
||||
.object({
|
||||
exit_node_name: z.string().optional(),
|
||||
@@ -233,7 +245,7 @@ export const configSchema = z
|
||||
.default(30)
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
.prefault({}),
|
||||
orgs: z
|
||||
.object({
|
||||
block_size: z.number().positive().gt(0).optional().default(24),
|
||||
@@ -262,7 +274,7 @@ export const configSchema = z
|
||||
.default(500)
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
.prefault({}),
|
||||
auth: z
|
||||
.object({
|
||||
window_minutes: z
|
||||
@@ -279,10 +291,10 @@ export const configSchema = z
|
||||
.default(500)
|
||||
})
|
||||
.optional()
|
||||
.default({})
|
||||
.prefault({})
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
.prefault({}),
|
||||
email: z
|
||||
.object({
|
||||
smtp_host: z.string().optional(),
|
||||
@@ -294,7 +306,7 @@ export const configSchema = z
|
||||
.transform(getEnvOrYaml("EMAIL_SMTP_PASS")),
|
||||
smtp_secure: z.boolean().optional(),
|
||||
smtp_tls_reject_unauthorized: z.boolean().optional(),
|
||||
no_reply: z.string().email().optional()
|
||||
no_reply: z.email().optional()
|
||||
})
|
||||
.optional(),
|
||||
flags: z
|
||||
@@ -315,11 +327,18 @@ export const configSchema = z
|
||||
nameservers: z
|
||||
.array(z.string().optional().optional())
|
||||
.optional()
|
||||
.default(["ns1.pangolin.net", "ns2.pangolin.net", "ns3.pangolin.net"]),
|
||||
cname_extension: z.string().optional().default("cname.pangolin.net")
|
||||
.default([
|
||||
"ns1.pangolin.net",
|
||||
"ns2.pangolin.net",
|
||||
"ns3.pangolin.net"
|
||||
]),
|
||||
cname_extension: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("cname.pangolin.net")
|
||||
})
|
||||
.optional()
|
||||
.default({})
|
||||
.prefault({})
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
@@ -334,7 +353,7 @@ export const configSchema = z
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "At least one domain must be defined"
|
||||
error: "At least one domain must be defined"
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
@@ -349,7 +368,7 @@ export const configSchema = z
|
||||
);
|
||||
},
|
||||
{
|
||||
message: "Server secret must be defined"
|
||||
error: "Server secret must be defined"
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
@@ -361,7 +380,7 @@ export const configSchema = z
|
||||
);
|
||||
},
|
||||
{
|
||||
message: "Dashboard URL must be defined"
|
||||
error: "Dashboard URL must be defined"
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ class TelemetryClient {
|
||||
license_tier: licenseStatus.tier || "unknown"
|
||||
}
|
||||
};
|
||||
logger.debug("Sending enterprise startup telemtry payload:", {
|
||||
logger.debug("Sending enterprise startup telemetry payload:", {
|
||||
payload
|
||||
});
|
||||
// this.client.capture(payload);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import z from "zod";
|
||||
import ipaddr from "ipaddr.js";
|
||||
|
||||
export function isValidCIDR(cidr: string): boolean {
|
||||
return z.string().cidr().safeParse(cidr).success;
|
||||
return z.cidrv4().safeParse(cidr).success || z.cidrv6().safeParse(cidr).success;
|
||||
}
|
||||
|
||||
export function isValidIP(ip: string): boolean {
|
||||
return z.string().ip().safeParse(ip).success;
|
||||
return z.ipv4().safeParse(ip).success || z.ipv6().safeParse(ip).success;
|
||||
}
|
||||
|
||||
export function isValidUrlGlobPattern(pattern: string): boolean {
|
||||
@@ -68,11 +69,11 @@ export function isUrlValid(url: string | undefined) {
|
||||
if (!url) return true; // the link is optional in the schema so if it's empty it's valid
|
||||
var pattern = new RegExp(
|
||||
"^(https?:\\/\\/)?" + // protocol
|
||||
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
|
||||
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
|
||||
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
|
||||
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
|
||||
"(\\#[-a-z\\d_]*)?$",
|
||||
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
|
||||
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
|
||||
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
|
||||
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
|
||||
"(\\#[-a-z\\d_]*)?$",
|
||||
"i"
|
||||
);
|
||||
return !!pattern.test(url);
|
||||
@@ -83,12 +84,15 @@ export function isTargetValid(value: string | undefined) {
|
||||
|
||||
const DOMAIN_REGEX =
|
||||
/^[a-zA-Z0-9_](?:[a-zA-Z0-9-_]{0,61}[a-zA-Z0-9_])?(?:\.[a-zA-Z0-9_](?:[a-zA-Z0-9-_]{0,61}[a-zA-Z0-9_])?)*$/;
|
||||
const IPV4_REGEX =
|
||||
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
const IPV6_REGEX = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/i;
|
||||
// const IPV4_REGEX =
|
||||
// /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
// const IPV6_REGEX = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/i;
|
||||
|
||||
if (IPV4_REGEX.test(value) || IPV6_REGEX.test(value)) {
|
||||
return true;
|
||||
try {
|
||||
const addr = ipaddr.parse(value);
|
||||
return addr.kind() === "ipv4" || addr.kind() === "ipv6";
|
||||
} catch {
|
||||
// fall through to domain regex check
|
||||
}
|
||||
|
||||
return DOMAIN_REGEX.test(value);
|
||||
@@ -169,10 +173,10 @@ export function isSecondLevelDomain(domain: string): boolean {
|
||||
}
|
||||
|
||||
const trimmedDomain = domain.trim().toLowerCase();
|
||||
|
||||
|
||||
// Split into parts
|
||||
const parts = trimmedDomain.split('.');
|
||||
|
||||
|
||||
// Should have exactly 2 parts for a second-level domain (e.g., "example.com")
|
||||
if (parts.length !== 2) {
|
||||
return false;
|
||||
|
||||
@@ -50,14 +50,14 @@ export const privateConfigSchema = z.object({
|
||||
host: z.string(),
|
||||
port: portSchema,
|
||||
password: z.string().optional(),
|
||||
db: z.number().int().nonnegative().optional().default(0),
|
||||
db: z.int().nonnegative().optional().default(0),
|
||||
replicas: z
|
||||
.array(
|
||||
z.object({
|
||||
host: z.string(),
|
||||
port: portSchema,
|
||||
password: z.string().optional(),
|
||||
db: z.number().int().nonnegative().optional().default(0)
|
||||
db: z.int().nonnegative().optional().default(0)
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
@@ -79,14 +79,14 @@ export const privateConfigSchema = z.object({
|
||||
.default("http://gerbil:3004")
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
.prefault({}),
|
||||
flags: z
|
||||
.object({
|
||||
enable_redis: z.boolean().optional().default(false),
|
||||
use_pangolin_dns: z.boolean().optional().default(false)
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
.prefault({}),
|
||||
branding: z
|
||||
.object({
|
||||
app_name: z.string().optional(),
|
||||
|
||||
@@ -30,17 +30,17 @@ export const queryAccessAuditLogsQuery = z.object({
|
||||
timeStart: z
|
||||
.string()
|
||||
.refine((val) => !isNaN(Date.parse(val)), {
|
||||
message: "timeStart must be a valid ISO date string"
|
||||
error: "timeStart must be a valid ISO date string"
|
||||
})
|
||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000)),
|
||||
timeEnd: z
|
||||
.string()
|
||||
.refine((val) => !isNaN(Date.parse(val)), {
|
||||
message: "timeEnd must be a valid ISO date string"
|
||||
error: "timeEnd must be a valid ISO date string"
|
||||
})
|
||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||
.optional()
|
||||
.default(new Date().toISOString()),
|
||||
.prefault(new Date().toISOString()),
|
||||
action: z
|
||||
.union([z.boolean(), z.string()])
|
||||
.transform((val) => (typeof val === "string" ? val === "true" : val))
|
||||
@@ -51,7 +51,7 @@ export const queryAccessAuditLogsQuery = z.object({
|
||||
.string()
|
||||
.optional()
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive())
|
||||
.pipe(z.int().positive())
|
||||
.optional(),
|
||||
actor: z.string().optional(),
|
||||
type: z.string().optional(),
|
||||
@@ -61,13 +61,13 @@ export const queryAccessAuditLogsQuery = z.object({
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive()),
|
||||
.pipe(z.int().positive()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
export const queryAccessAuditLogsParams = z.object({
|
||||
|
||||
@@ -30,17 +30,17 @@ export const queryActionAuditLogsQuery = z.object({
|
||||
timeStart: z
|
||||
.string()
|
||||
.refine((val) => !isNaN(Date.parse(val)), {
|
||||
message: "timeStart must be a valid ISO date string"
|
||||
error: "timeStart must be a valid ISO date string"
|
||||
})
|
||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000)),
|
||||
timeEnd: z
|
||||
.string()
|
||||
.refine((val) => !isNaN(Date.parse(val)), {
|
||||
message: "timeEnd must be a valid ISO date string"
|
||||
error: "timeEnd must be a valid ISO date string"
|
||||
})
|
||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||
.optional()
|
||||
.default(new Date().toISOString()),
|
||||
.prefault(new Date().toISOString()),
|
||||
action: z.string().optional(),
|
||||
actorType: z.string().optional(),
|
||||
actorId: z.string().optional(),
|
||||
@@ -50,13 +50,13 @@ export const queryActionAuditLogsQuery = z.object({
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive()),
|
||||
.pipe(z.int().positive()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
export const queryActionAuditLogsParams = z.object({
|
||||
|
||||
@@ -28,7 +28,7 @@ import { response } from "@server/lib/response";
|
||||
import { encrypt } from "@server/lib/crypto";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
const paramsSchema = z.object({}).strict();
|
||||
const paramsSchema = z.strictObject({});
|
||||
|
||||
export type GetSessionTransferTokenRenponse = {
|
||||
token: string;
|
||||
|
||||
@@ -62,10 +62,10 @@ import { isTargetValid } from "@server/lib/validators";
|
||||
import { listExitNodes } from "#private/lib/exitNodes";
|
||||
|
||||
const bodySchema = z.object({
|
||||
email: z.string().toLowerCase().email(),
|
||||
email: z.email().toLowerCase(),
|
||||
ip: z.string().refine(isTargetValid),
|
||||
method: z.enum(["http", "https"]),
|
||||
port: z.number().int().min(1).max(65535),
|
||||
port: z.int().min(1).max(65535),
|
||||
pincode: z
|
||||
.string()
|
||||
.regex(/^\d{6}$/)
|
||||
|
||||
@@ -25,11 +25,9 @@ import stripe from "#private/lib/stripe";
|
||||
import { getLineItems, getStandardFeaturePriceSet } from "@server/lib/billing";
|
||||
import { getTierPriceSet, TierId } from "@server/lib/billing/tiers";
|
||||
|
||||
const createCheckoutSessionSchema = z
|
||||
.object({
|
||||
const createCheckoutSessionSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export async function createCheckoutSession(
|
||||
req: Request,
|
||||
|
||||
@@ -23,11 +23,9 @@ import config from "@server/lib/config";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import stripe from "#private/lib/stripe";
|
||||
|
||||
const createPortalSessionSchema = z
|
||||
.object({
|
||||
const createPortalSessionSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export async function createPortalSession(
|
||||
req: Request,
|
||||
|
||||
@@ -33,11 +33,9 @@ import {
|
||||
SubscriptionItem
|
||||
} from "@server/db";
|
||||
|
||||
const getOrgSchema = z
|
||||
.object({
|
||||
const getOrgSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
|
||||
@@ -27,11 +27,9 @@ import { usageService } from "@server/lib/billing/usageService";
|
||||
import { FeatureId } from "@server/lib/billing";
|
||||
import { GetOrgUsageResponse } from "@server/routers/billing/types";
|
||||
|
||||
const getOrgSchema = z
|
||||
.object({
|
||||
const getOrgSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
|
||||
@@ -21,11 +21,9 @@ import { fromZodError } from "zod-validation-error";
|
||||
import { getOrgTierData } from "#private/lib/billing";
|
||||
import { GetOrgTierResponse } from "@server/routers/billing/types";
|
||||
|
||||
const getOrgSchema = z
|
||||
.object({
|
||||
const getOrgSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export async function getOrgTier(
|
||||
req: Request,
|
||||
|
||||
@@ -23,13 +23,11 @@ import { fromError } from "zod-validation-error";
|
||||
import { registry } from "@server/openApi";
|
||||
import { GetCertificateResponse } from "@server/routers/certificates/types";
|
||||
|
||||
const getCertificateSchema = z
|
||||
.object({
|
||||
const getCertificateSchema = z.strictObject({
|
||||
domainId: z.string(),
|
||||
domain: z.string().min(1).max(255),
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
async function query(domainId: string, domain: string) {
|
||||
const [domainRecord] = await db
|
||||
|
||||
@@ -24,12 +24,10 @@ import stoi from "@server/lib/stoi";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const restartCertificateParamsSchema = z
|
||||
.object({
|
||||
certId: z.string().transform(stoi).pipe(z.number().int().positive()),
|
||||
const restartCertificateParamsSchema = z.strictObject({
|
||||
certId: z.string().transform(stoi).pipe(z.int().positive()),
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
@@ -41,7 +39,7 @@ registry.registerPath({
|
||||
certId: z
|
||||
.string()
|
||||
.transform(stoi)
|
||||
.pipe(z.number().int().positive()),
|
||||
.pipe(z.int().positive()),
|
||||
orgId: z.string()
|
||||
})
|
||||
},
|
||||
|
||||
@@ -23,13 +23,11 @@ import { db, domainNamespaces, resources } from "@server/db";
|
||||
import { inArray } from "drizzle-orm";
|
||||
import { CheckDomainAvailabilityResponse } from "@server/routers/domain/types";
|
||||
|
||||
const paramsSchema = z.object({}).strict();
|
||||
const paramsSchema = z.strictObject({});
|
||||
|
||||
const querySchema = z
|
||||
.object({
|
||||
const querySchema = z.strictObject({
|
||||
subdomain: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
|
||||
@@ -23,24 +23,22 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const paramsSchema = z.object({}).strict();
|
||||
const paramsSchema = z.strictObject({});
|
||||
|
||||
const querySchema = z
|
||||
.object({
|
||||
const querySchema = z.strictObject({
|
||||
limit: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative()),
|
||||
.pipe(z.int().nonnegative()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
})
|
||||
.strict();
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
async function query(limit: number, offset: number) {
|
||||
const res = await db
|
||||
|
||||
@@ -23,11 +23,15 @@ import * as license from "#private/routers/license";
|
||||
import * as generateLicense from "./generatedLicense";
|
||||
import * as logs from "#private/routers/auditLogs";
|
||||
import * as misc from "#private/routers/misc";
|
||||
import * as reKey from "#private/routers/re-key";
|
||||
|
||||
import {
|
||||
verifyOrgAccess,
|
||||
verifyUserHasAction,
|
||||
verifyUserIsServerAdmin
|
||||
verifyUserIsServerAdmin,
|
||||
verifySiteAccess,
|
||||
verifyClientAccess,
|
||||
verifyClientsEnabled,
|
||||
} from "@server/middlewares";
|
||||
import { ActionsEnum } from "@server/auth/actions";
|
||||
import {
|
||||
@@ -403,3 +407,26 @@ authenticated.get(
|
||||
logActionAudit(ActionsEnum.exportLogs),
|
||||
logs.exportAccessAuditLogs
|
||||
);
|
||||
|
||||
authenticated.post(
|
||||
"/re-key/:clientId/regenerate-client-secret",
|
||||
verifyClientsEnabled,
|
||||
verifyClientAccess,
|
||||
verifyUserHasAction(ActionsEnum.reGenerateSecret),
|
||||
reKey.reGenerateClientSecret
|
||||
);
|
||||
|
||||
authenticated.post(
|
||||
"/re-key/:siteId/regenerate-site-secret",
|
||||
verifySiteAccess,
|
||||
verifyUserHasAction(ActionsEnum.reGenerateSecret),
|
||||
reKey.reGenerateSiteSecret
|
||||
);
|
||||
|
||||
authenticated.put(
|
||||
"/re-key/:orgId/reGenerate-remote-exit-node-secret",
|
||||
verifyValidLicense,
|
||||
verifyOrgAccess,
|
||||
verifyUserHasAction(ActionsEnum.updateRemoteExitNode),
|
||||
reKey.reGenerateExitNodeSecret
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ async function createNewLicense(orgId: string, licenseData: any): Promise<any> {
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
logger.debug("Fossorial API response:", {data});
|
||||
logger.debug("Fossorial API response:", { data });
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error creating new license:", error);
|
||||
|
||||
@@ -17,7 +17,10 @@ import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { response as sendResponse } from "@server/lib/response";
|
||||
import privateConfig from "#private/lib/config";
|
||||
import { GeneratedLicenseKey, ListGeneratedLicenseKeysResponse } from "@server/routers/generatedLicense/types";
|
||||
import {
|
||||
GeneratedLicenseKey,
|
||||
ListGeneratedLicenseKeysResponse
|
||||
} from "@server/routers/generatedLicense/types";
|
||||
|
||||
async function fetchLicenseKeys(orgId: string): Promise<any> {
|
||||
try {
|
||||
|
||||
@@ -78,105 +78,78 @@ import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToke
|
||||
import semver from "semver";
|
||||
|
||||
// Zod schemas for request validation
|
||||
const getResourceByDomainParamsSchema = z
|
||||
.object({
|
||||
const getResourceByDomainParamsSchema = z.strictObject({
|
||||
domain: z.string().min(1, "Domain is required")
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const getUserSessionParamsSchema = z
|
||||
.object({
|
||||
const getUserSessionParamsSchema = z.strictObject({
|
||||
userSessionId: z.string().min(1, "User session ID is required")
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const getUserOrgRoleParamsSchema = z
|
||||
.object({
|
||||
const getUserOrgRoleParamsSchema = z.strictObject({
|
||||
userId: z.string().min(1, "User ID is required"),
|
||||
orgId: z.string().min(1, "Organization ID is required")
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const getRoleResourceAccessParamsSchema = z
|
||||
.object({
|
||||
const getRoleResourceAccessParamsSchema = z.strictObject({
|
||||
roleId: z
|
||||
.string()
|
||||
.transform(Number)
|
||||
.pipe(
|
||||
z.number().int().positive("Role ID must be a positive integer")
|
||||
z.int().positive("Role ID must be a positive integer")
|
||||
),
|
||||
resourceId: z
|
||||
.string()
|
||||
.transform(Number)
|
||||
.pipe(
|
||||
z
|
||||
.number()
|
||||
.int()
|
||||
z.int()
|
||||
.positive("Resource ID must be a positive integer")
|
||||
)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const getUserResourceAccessParamsSchema = z
|
||||
.object({
|
||||
const getUserResourceAccessParamsSchema = z.strictObject({
|
||||
userId: z.string().min(1, "User ID is required"),
|
||||
resourceId: z
|
||||
.string()
|
||||
.transform(Number)
|
||||
.pipe(
|
||||
z
|
||||
.number()
|
||||
.int()
|
||||
z.int()
|
||||
.positive("Resource ID must be a positive integer")
|
||||
)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const getResourceRulesParamsSchema = z
|
||||
.object({
|
||||
const getResourceRulesParamsSchema = z.strictObject({
|
||||
resourceId: z
|
||||
.string()
|
||||
.transform(Number)
|
||||
.pipe(
|
||||
z
|
||||
.number()
|
||||
.int()
|
||||
z.int()
|
||||
.positive("Resource ID must be a positive integer")
|
||||
)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const validateResourceSessionTokenParamsSchema = z
|
||||
.object({
|
||||
const validateResourceSessionTokenParamsSchema = z.strictObject({
|
||||
resourceId: z
|
||||
.string()
|
||||
.transform(Number)
|
||||
.pipe(
|
||||
z
|
||||
.number()
|
||||
.int()
|
||||
z.int()
|
||||
.positive("Resource ID must be a positive integer")
|
||||
)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const validateResourceSessionTokenBodySchema = z
|
||||
.object({
|
||||
const validateResourceSessionTokenBodySchema = z.strictObject({
|
||||
token: z.string().min(1, "Token is required")
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const validateResourceAccessTokenBodySchema = z
|
||||
.object({
|
||||
const validateResourceAccessTokenBodySchema = z.strictObject({
|
||||
accessTokenId: z.string().optional(),
|
||||
resourceId: z.number().optional(),
|
||||
accessToken: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
// Certificates by domains query validation
|
||||
const getCertificatesByDomainsQuerySchema = z
|
||||
.object({
|
||||
const getCertificatesByDomainsQuerySchema = z.strictObject({
|
||||
// Accept domains as string or array (domains or domains[])
|
||||
domains: z
|
||||
.union([z.array(z.string().min(1)), z.string().min(1)])
|
||||
@@ -185,8 +158,7 @@ const getCertificatesByDomainsQuerySchema = z
|
||||
"domains[]": z
|
||||
.union([z.array(z.string().min(1)), z.string().min(1)])
|
||||
.optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
// Type exports for request schemas
|
||||
export type GetResourceByDomainParams = z.infer<
|
||||
@@ -591,11 +563,9 @@ hybridRouter.get(
|
||||
}
|
||||
);
|
||||
|
||||
const getOrgLoginPageParamsSchema = z
|
||||
.object({
|
||||
const getOrgLoginPageParamsSchema = z.strictObject({
|
||||
orgId: z.string().min(1)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
hybridRouter.get(
|
||||
"/org/:orgId/login-page",
|
||||
@@ -1217,7 +1187,7 @@ hybridRouter.post(
|
||||
);
|
||||
|
||||
const geoIpLookupParamsSchema = z.object({
|
||||
ip: z.string().ip()
|
||||
ip: z.union([z.ipv4(), z.ipv6()])
|
||||
});
|
||||
hybridRouter.get(
|
||||
"/geoip/:ip",
|
||||
|
||||
@@ -20,11 +20,9 @@ import license from "#private/license/license";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
licenseKey: z.string().min(1).max(255)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export async function activateLicense(
|
||||
req: Request,
|
||||
|
||||
@@ -23,11 +23,9 @@ import { eq } from "drizzle-orm";
|
||||
import { licenseKey } from "@server/db";
|
||||
import license from "#private/license/license";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const paramsSchema = z.strictObject({
|
||||
licenseKey: z.string().min(1).max(255)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export async function deleteLicenseKey(
|
||||
req: Request,
|
||||
|
||||
@@ -35,18 +35,14 @@ import { TierId } from "@server/lib/billing/tiers";
|
||||
import { build } from "@server/build";
|
||||
import { CreateLoginPageResponse } from "@server/routers/loginPage/types";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
subdomain: z.string().nullable().optional(),
|
||||
domainId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type CreateLoginPageBody = z.infer<typeof bodySchema>;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import { DeleteLoginPageResponse } from "@server/routers/loginPage/types";
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
orgId: z.string(),
|
||||
loginPageId: z.coerce.number()
|
||||
loginPageId: z.coerce.number<number>()
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
@@ -22,11 +22,9 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { GetLoginPageResponse } from "@server/routers/loginPage/types";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
async function query(orgId: string) {
|
||||
const [res] = await db
|
||||
|
||||
@@ -23,8 +23,8 @@ import { fromError } from "zod-validation-error";
|
||||
import { LoadLoginPageResponse } from "@server/routers/loginPage/types";
|
||||
|
||||
const querySchema = z.object({
|
||||
resourceId: z.coerce.number().int().positive().optional(),
|
||||
idpId: z.coerce.number().int().positive().optional(),
|
||||
resourceId: z.coerce.number<number>().int().positive().optional(),
|
||||
idpId: z.coerce.number<number>().int().positive().optional(),
|
||||
orgId: z.string().min(1).optional(),
|
||||
fullDomain: z.string().min(1)
|
||||
});
|
||||
|
||||
@@ -31,18 +31,16 @@ import { UpdateLoginPageResponse } from "@server/routers/loginPage/types";
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
orgId: z.string(),
|
||||
loginPageId: z.coerce.number()
|
||||
loginPageId: z.coerce.number<number>()
|
||||
})
|
||||
.strict();
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
subdomain: subdomainSchema.nullable().optional(),
|
||||
domainId: z.string().optional()
|
||||
})
|
||||
.strict()
|
||||
.refine((data) => Object.keys(data).length > 0, {
|
||||
message: "At least one field must be provided for update"
|
||||
error: "At least one field must be provided for update"
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
@@ -51,7 +49,9 @@ const bodySchema = z
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{ message: "Invalid subdomain" }
|
||||
{
|
||||
error: "Invalid subdomain"
|
||||
}
|
||||
);
|
||||
|
||||
export type UpdateLoginPageBody = z.infer<typeof bodySchema>;
|
||||
|
||||
@@ -22,12 +22,10 @@ import { sendEmail } from "@server/emails";
|
||||
import SupportEmail from "@server/emails/templates/SupportEmail";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
body: z.string().min(1),
|
||||
subject: z.string().min(1).max(255)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export async function sendSupportEmail(
|
||||
req: Request,
|
||||
@@ -68,7 +66,7 @@ export async function sendSupportEmail(
|
||||
{
|
||||
name: req.user?.email || "Support User",
|
||||
to: "support@pangolin.net",
|
||||
from: req.user?.email || config.getNoReplyEmail(),
|
||||
from: config.getNoReplyEmail(),
|
||||
subject: `Support Request: ${subject}`
|
||||
}
|
||||
);
|
||||
|
||||
@@ -29,15 +29,14 @@ import { getOrgTierData } from "#private/lib/billing";
|
||||
import { TierId } from "@server/lib/billing/tiers";
|
||||
import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types";
|
||||
|
||||
const paramsSchema = z.object({ orgId: z.string().nonempty() }).strict();
|
||||
const paramsSchema = z.strictObject({ orgId: z.string().nonempty() });
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
name: z.string().nonempty(),
|
||||
clientId: z.string().nonempty(),
|
||||
clientSecret: z.string().nonempty(),
|
||||
authUrl: z.string().url(),
|
||||
tokenUrl: z.string().url(),
|
||||
authUrl: z.url(),
|
||||
tokenUrl: z.url(),
|
||||
identifierPath: z.string().nonempty(),
|
||||
emailPath: z.string().optional(),
|
||||
namePath: z.string().optional(),
|
||||
@@ -45,8 +44,7 @@ const bodySchema = z
|
||||
autoProvision: z.boolean().optional(),
|
||||
variant: z.enum(["oidc", "google", "azure"]).optional().default("oidc"),
|
||||
roleMapping: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
// registry.registerPath({
|
||||
// method: "put",
|
||||
|
||||
@@ -26,7 +26,7 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
orgId: z.string().optional(), // Optional; used with org idp in saas
|
||||
idpId: z.coerce.number()
|
||||
idpId: z.coerce.number<number>()
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import { GetOrgIdpResponse } from "@server/routers/orgIdp/types";
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
orgId: z.string().nonempty(),
|
||||
idpId: z.coerce.number()
|
||||
idpId: z.coerce.number<number>()
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
@@ -24,28 +24,24 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { ListOrgIdpsResponse } from "@server/routers/orgIdp/types";
|
||||
|
||||
const querySchema = z
|
||||
.object({
|
||||
const querySchema = z.strictObject({
|
||||
limit: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative()),
|
||||
.pipe(z.int().nonnegative()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
})
|
||||
.strict();
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().nonempty()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
async function query(orgId: string, limit: number, offset: number) {
|
||||
const res = await db
|
||||
|
||||
@@ -31,12 +31,11 @@ import { TierId } from "@server/lib/billing/tiers";
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
orgId: z.string().nonempty(),
|
||||
idpId: z.coerce.number()
|
||||
idpId: z.coerce.number<number>()
|
||||
})
|
||||
.strict();
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
name: z.string().optional(),
|
||||
clientId: z.string().optional(),
|
||||
clientSecret: z.string().optional(),
|
||||
@@ -48,8 +47,7 @@ const bodySchema = z
|
||||
scopes: z.string().optional(),
|
||||
autoProvision: z.boolean().optional(),
|
||||
roleMapping: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type UpdateOrgIdpResponse = {
|
||||
idpId: number;
|
||||
|
||||
16
server/private/routers/re-key/index.ts
Normal file
16
server/private/routers/re-key/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025 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 "./reGenerateClientSecret";
|
||||
export * from "./reGenerateSiteSecret";
|
||||
export * from "./reGenerateExitNodeSecret";
|
||||
139
server/private/routers/re-key/reGenerateClientSecret.ts
Normal file
139
server/private/routers/re-key/reGenerateClientSecret.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025 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 { db, olms, } from "@server/db";
|
||||
import { clients } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { hashPassword } from "@server/auth/password";
|
||||
|
||||
const reGenerateSecretParamsSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
});
|
||||
|
||||
const reGenerateSecretBodySchema = z.strictObject({
|
||||
olmId: z.string().min(1).optional(),
|
||||
secret: z.string().min(1).optional(),
|
||||
|
||||
});
|
||||
|
||||
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,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedBody = reGenerateSecretBodySchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedBody.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { olmId, secret } = parsedBody.data;
|
||||
|
||||
const parsedParams = reGenerateSecretParamsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { clientId } = parsedParams.data;
|
||||
|
||||
let secretHash = undefined;
|
||||
if (secret) {
|
||||
secretHash = await hashPassword(secret);
|
||||
}
|
||||
|
||||
|
||||
// Fetch the client to make sure it exists and the user has access to it
|
||||
const [client] = await db
|
||||
.select()
|
||||
.from(clients)
|
||||
.where(eq(clients.clientId, clientId))
|
||||
.limit(1);
|
||||
|
||||
if (!client) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Client with ID ${clientId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [existingOlm] = await db
|
||||
.select()
|
||||
.from(olms)
|
||||
.where(eq(olms.clientId, clientId))
|
||||
.limit(1);
|
||||
|
||||
if (existingOlm && olmId && secretHash) {
|
||||
await db
|
||||
.update(olms)
|
||||
.set({
|
||||
olmId,
|
||||
secretHash
|
||||
})
|
||||
.where(eq(olms.clientId, clientId));
|
||||
}
|
||||
|
||||
return response(res, {
|
||||
data: existingOlm,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Credentials regenerated successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
127
server/private/routers/re-key/reGenerateExitNodeSecret.ts
Normal file
127
server/private/routers/re-key/reGenerateExitNodeSecret.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025 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 { NextFunction, Request, Response } from "express";
|
||||
import { db, exitNodes, exitNodeOrgs, ExitNode, ExitNodeOrg } from "@server/db";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { z } from "zod";
|
||||
import { remoteExitNodes } from "@server/db";
|
||||
import createHttpError from "http-errors";
|
||||
import response from "@server/lib/response";
|
||||
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";
|
||||
|
||||
export const paramsSchema = z.object({
|
||||
orgId: z.string()
|
||||
});
|
||||
|
||||
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: {}
|
||||
});
|
||||
|
||||
export async function reGenerateExitNodeSecret(
|
||||
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 parsedBody = bodySchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedBody.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { remoteExitNodeId, secret } = parsedBody.data;
|
||||
|
||||
if (req.user && !req.userOrgRoleId) {
|
||||
return next(
|
||||
createHttpError(HttpCode.FORBIDDEN, "User does not have a role")
|
||||
);
|
||||
}
|
||||
|
||||
const [existingRemoteExitNode] = await db
|
||||
.select()
|
||||
.from(remoteExitNodes)
|
||||
.where(eq(remoteExitNodes.remoteExitNodeId, remoteExitNodeId));
|
||||
|
||||
if (!existingRemoteExitNode) {
|
||||
return next(
|
||||
createHttpError(HttpCode.NOT_FOUND, "Remote Exit Node does not exist")
|
||||
);
|
||||
}
|
||||
|
||||
const secretHash = await hashPassword(secret);
|
||||
|
||||
await db
|
||||
.update(remoteExitNodes)
|
||||
.set({ secretHash })
|
||||
.where(eq(remoteExitNodes.remoteExitNodeId, remoteExitNodeId));
|
||||
|
||||
return response<UpdateRemoteExitNodeResponse>(res, {
|
||||
data: {
|
||||
remoteExitNodeId,
|
||||
secret,
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Remote Exit Node secret updated successfully",
|
||||
status: HttpCode.OK,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error("Failed to update remoteExitNode", e);
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Failed to update remoteExitNode"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
164
server/private/routers/re-key/reGenerateSiteSecret.ts
Normal file
164
server/private/routers/re-key/reGenerateSiteSecret.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025 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 { db, newts, sites } 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 { OpenAPITags, registry } from "@server/openApi";
|
||||
import { hashPassword } from "@server/auth/password";
|
||||
import { addPeer } from "@server/routers/gerbil/peers";
|
||||
|
||||
|
||||
const updateSiteParamsSchema = z.strictObject({
|
||||
siteId: z.string().transform(Number).pipe(z.int().positive())
|
||||
});
|
||||
|
||||
const updateSiteBodySchema = z.strictObject({
|
||||
type: z.enum(["newt", "wireguard"]),
|
||||
newtId: z.string().min(1).max(255).optional(),
|
||||
newtSecret: z.string().min(1).max(255).optional(),
|
||||
exitNodeId: z.int().positive().optional(),
|
||||
pubKey: z.string().optional(),
|
||||
subnet: 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: {},
|
||||
});
|
||||
|
||||
export async function reGenerateSiteSecret(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedParams = updateSiteParamsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, fromError(parsedParams.error).toString())
|
||||
);
|
||||
}
|
||||
|
||||
const parsedBody = updateSiteBodySchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, fromError(parsedBody.error).toString())
|
||||
);
|
||||
}
|
||||
|
||||
const { siteId } = parsedParams.data;
|
||||
const { type, exitNodeId, pubKey, subnet, newtId, newtSecret } = parsedBody.data;
|
||||
|
||||
let updatedSite = undefined;
|
||||
|
||||
if (type === "newt") {
|
||||
if (!newtSecret) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "newtSecret is required for newt sites")
|
||||
);
|
||||
}
|
||||
|
||||
const secretHash = await hashPassword(newtSecret);
|
||||
|
||||
updatedSite = await db
|
||||
.update(newts)
|
||||
.set({
|
||||
newtId,
|
||||
secretHash,
|
||||
})
|
||||
.where(eq(newts.siteId, siteId))
|
||||
.returning();
|
||||
|
||||
logger.info(`Regenerated Newt credentials for site ${siteId}`);
|
||||
|
||||
} else if (type === "wireguard") {
|
||||
if (!pubKey) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "Public key is required for wireguard sites")
|
||||
);
|
||||
}
|
||||
|
||||
if (!exitNodeId) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Exit node ID is required for wireguard sites"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
updatedSite = await db.transaction(async (tx) => {
|
||||
await addPeer(exitNodeId, {
|
||||
publicKey: pubKey,
|
||||
allowedIps: subnet ? [subnet] : [],
|
||||
});
|
||||
const result = await tx
|
||||
.update(sites)
|
||||
.set({ pubKey })
|
||||
.where(eq(sites.siteId, siteId))
|
||||
.returning();
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
logger.info(`Regenerated WireGuard credentials for site ${siteId}`);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`Transaction failed while regenerating WireGuard secret for site ${siteId}`,
|
||||
err
|
||||
);
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Failed to regenerate WireGuard credentials. Rolled back transaction."
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return response(res, {
|
||||
data: updatedSite,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Credentials regenerated successfully",
|
||||
status: HttpCode.OK,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error("Unexpected error in reGenerateSiteSecret", error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An unexpected error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -35,12 +35,10 @@ export const paramsSchema = z.object({
|
||||
orgId: z.string()
|
||||
});
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
remoteExitNodeId: z.string().length(15),
|
||||
secret: z.string().length(48)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type CreateRemoteExitNodeBody = z.infer<typeof bodySchema>;
|
||||
|
||||
|
||||
@@ -24,12 +24,10 @@ import { fromError } from "zod-validation-error";
|
||||
import { usageService } from "@server/lib/billing/usageService";
|
||||
import { FeatureId } from "@server/lib/billing";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string().min(1),
|
||||
remoteExitNodeId: z.string().min(1)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export async function deleteRemoteExitNode(
|
||||
req: Request,
|
||||
|
||||
@@ -23,12 +23,10 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { GetRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types";
|
||||
|
||||
const getRemoteExitNodeSchema = z
|
||||
.object({
|
||||
const getRemoteExitNodeSchema = z.strictObject({
|
||||
orgId: z.string().min(1),
|
||||
remoteExitNodeId: z.string().min(1)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
async function query(remoteExitNodeId: string) {
|
||||
const [remoteExitNode] = await db
|
||||
|
||||
@@ -23,11 +23,9 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types";
|
||||
|
||||
const listRemoteExitNodesParamsSchema = z
|
||||
.object({
|
||||
const listRemoteExitNodesParamsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const listRemoteExitNodesSchema = z.object({
|
||||
limit: z
|
||||
@@ -35,13 +33,13 @@ const listRemoteExitNodesSchema = z.object({
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive()),
|
||||
.pipe(z.int().positive()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
export function queryRemoteExitNodes(orgId: string) {
|
||||
|
||||
@@ -21,11 +21,9 @@ import { fromError } from "zod-validation-error";
|
||||
import { z } from "zod";
|
||||
import { PickRemoteExitNodeDefaultsResponse } from "@server/routers/remoteExitNode/types";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export async function pickRemoteExitNodeDefaults(
|
||||
req: Request,
|
||||
|
||||
@@ -10,11 +10,9 @@ import { and, eq } from "drizzle-orm";
|
||||
import { db } from "@server/db";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const deleteAccessTokenParamsSchema = z
|
||||
.object({
|
||||
const deleteAccessTokenParamsSchema = z.strictObject({
|
||||
accessTokenId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "delete",
|
||||
|
||||
@@ -24,22 +24,18 @@ import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
export const generateAccessTokenBodySchema = z
|
||||
.object({
|
||||
validForSeconds: z.number().int().positive().optional(), // seconds
|
||||
export const generateAccessTokenBodySchema = z.strictObject({
|
||||
validForSeconds: z.int().positive().optional(), // seconds
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export const generateAccssTokenParamsSchema = z
|
||||
.object({
|
||||
export const generateAccssTokenParamsSchema = z.strictObject({
|
||||
resourceId: z
|
||||
.string()
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive())
|
||||
})
|
||||
.strict();
|
||||
.pipe(z.int().positive())
|
||||
});
|
||||
|
||||
export type GenerateAccessTokenResponse = Omit<
|
||||
ResourceAccessToken,
|
||||
|
||||
@@ -17,18 +17,16 @@ import stoi from "@server/lib/stoi";
|
||||
import { fromZodError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const listAccessTokensParamsSchema = z
|
||||
.object({
|
||||
const listAccessTokensParamsSchema = z.strictObject({
|
||||
resourceId: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform(stoi)
|
||||
.pipe(z.number().int().positive().optional()),
|
||||
.pipe(z.int().positive().optional()),
|
||||
orgId: z.string().optional()
|
||||
})
|
||||
.strict()
|
||||
.refine((data) => !!data.resourceId !== !!data.orgId, {
|
||||
message: "Either resourceId or orgId must be provided, but not both"
|
||||
error: "Either resourceId or orgId must be provided, but not both"
|
||||
});
|
||||
|
||||
const listAccessTokensSchema = z.object({
|
||||
@@ -37,14 +35,14 @@ const listAccessTokensSchema = z.object({
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative()),
|
||||
.pipe(z.int().nonnegative()),
|
||||
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
function queryAccessTokens(
|
||||
|
||||
@@ -14,11 +14,9 @@ import {
|
||||
import logger from "@server/logger";
|
||||
import { hashPassword } from "@server/auth/password";
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
name: z.string().min(1).max(255)
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type CreateRootApiKeyBody = z.infer<typeof bodySchema>;
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ const querySchema = z.object({
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive()),
|
||||
.pipe(z.int().positive()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
function queryActions(apiKeyId: string) {
|
||||
|
||||
@@ -16,13 +16,13 @@ const querySchema = z.object({
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive()),
|
||||
.pipe(z.int().positive()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
const paramsSchema = z.object({
|
||||
|
||||
@@ -15,13 +15,13 @@ const querySchema = z.object({
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive()),
|
||||
.pipe(z.int().positive()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
function queryApiKeys() {
|
||||
|
||||
@@ -10,13 +10,10 @@ import { fromError } from "zod-validation-error";
|
||||
import { eq, and, inArray } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
actionIds: z
|
||||
.array(z.string().nonempty())
|
||||
const bodySchema = z.strictObject({
|
||||
actionIds: z.tuple([z.string()], z.string())
|
||||
.transform((v) => Array.from(new Set(v)))
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const paramsSchema = z.object({
|
||||
apiKeyId: z.string().nonempty()
|
||||
|
||||
@@ -9,13 +9,10 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { eq, and, inArray } from "drizzle-orm";
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
orgIds: z
|
||||
.array(z.string().nonempty())
|
||||
const bodySchema = z.strictObject({
|
||||
orgIds: z.tuple([z.string()], z.string())
|
||||
.transform((v) => Array.from(new Set(v)))
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const paramsSchema = z.object({
|
||||
apiKeyId: z.string().nonempty()
|
||||
|
||||
@@ -17,17 +17,17 @@ export const queryAccessAuditLogsQuery = z.object({
|
||||
timeStart: z
|
||||
.string()
|
||||
.refine((val) => !isNaN(Date.parse(val)), {
|
||||
message: "timeStart must be a valid ISO date string"
|
||||
error: "timeStart must be a valid ISO date string"
|
||||
})
|
||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000)),
|
||||
timeEnd: z
|
||||
.string()
|
||||
.refine((val) => !isNaN(Date.parse(val)), {
|
||||
message: "timeEnd must be a valid ISO date string"
|
||||
error: "timeEnd must be a valid ISO date string"
|
||||
})
|
||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||
.optional()
|
||||
.default(new Date().toISOString()),
|
||||
.prefault(new Date().toISOString()),
|
||||
action: z
|
||||
.union([z.boolean(), z.string()])
|
||||
.transform((val) => (typeof val === "string" ? val === "true" : val))
|
||||
@@ -37,13 +37,13 @@ export const queryAccessAuditLogsQuery = z.object({
|
||||
.string()
|
||||
.optional()
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive())
|
||||
.pipe(z.int().positive())
|
||||
.optional(),
|
||||
resourceId: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive())
|
||||
.pipe(z.int().positive())
|
||||
.optional(),
|
||||
actor: z.string().optional(),
|
||||
location: z.string().optional(),
|
||||
@@ -54,13 +54,13 @@ export const queryAccessAuditLogsQuery = z.object({
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive()),
|
||||
.pipe(z.int().positive()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
export const queryRequestAuditLogsParams = z.object({
|
||||
|
||||
@@ -22,13 +22,11 @@ import { sendEmail } from "@server/emails";
|
||||
import ConfirmPasswordReset from "@server/emails/templates/NotifyResetPassword";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
export const changePasswordBody = z
|
||||
.object({
|
||||
export const changePasswordBody = z.strictObject({
|
||||
oldPassword: z.string(),
|
||||
newPassword: passwordSchema,
|
||||
code: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type ChangePasswordBody = z.infer<typeof changePasswordBody>;
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import { response } from "@server/lib/response";
|
||||
import { validateResourceSessionToken } from "@server/auth/sessions/resource";
|
||||
import logger from "@server/logger";
|
||||
|
||||
export const params = z.object({
|
||||
export const params = z.strictObject({
|
||||
token: z.string(),
|
||||
resourceId: z.string().transform(Number).pipe(z.number().int().positive()),
|
||||
}).strict();
|
||||
resourceId: z.string().transform(Number).pipe(z.int().positive()),
|
||||
});
|
||||
|
||||
export type CheckResourceSessionParams = z.infer<typeof params>;
|
||||
|
||||
|
||||
@@ -16,12 +16,10 @@ import config from "@server/lib/config";
|
||||
import { unauthorized } from "@server/auth/unauthorizedResponse";
|
||||
import { UserType } from "@server/types/UserTypes";
|
||||
|
||||
export const disable2faBody = z
|
||||
.object({
|
||||
export const disable2faBody = z.strictObject({
|
||||
password: z.string(),
|
||||
code: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type Disable2faBody = z.infer<typeof disable2faBody>;
|
||||
|
||||
|
||||
@@ -20,14 +20,12 @@ import { verifySession } from "@server/auth/sessions/verifySession";
|
||||
import { UserType } from "@server/types/UserTypes";
|
||||
import { logAccessAudit } from "#dynamic/lib/logAccessAudit";
|
||||
|
||||
export const loginBodySchema = z
|
||||
.object({
|
||||
email: z.string().toLowerCase().email(),
|
||||
export const loginBodySchema = z.strictObject({
|
||||
email: z.email().toLowerCase(),
|
||||
password: z.string(),
|
||||
code: z.string().optional(),
|
||||
resourceGuid: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type LoginBody = z.infer<typeof loginBodySchema>;
|
||||
|
||||
|
||||
@@ -17,11 +17,9 @@ import ResetPasswordCode from "@server/emails/templates/ResetPasswordCode";
|
||||
import { hashPassword } from "@server/auth/password";
|
||||
import { UserType } from "@server/types/UserTypes";
|
||||
|
||||
export const requestPasswordResetBody = z
|
||||
.object({
|
||||
email: z.string().toLowerCase().email()
|
||||
})
|
||||
.strict();
|
||||
export const requestPasswordResetBody = z.strictObject({
|
||||
email: z.email().toLowerCase()
|
||||
});
|
||||
|
||||
export type RequestPasswordResetBody = z.infer<typeof requestPasswordResetBody>;
|
||||
|
||||
|
||||
@@ -16,12 +16,10 @@ import { UserType } from "@server/types/UserTypes";
|
||||
import { verifySession } from "@server/auth/sessions/verifySession";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
export const requestTotpSecretBody = z
|
||||
.object({
|
||||
export const requestTotpSecretBody = z.strictObject({
|
||||
password: z.string(),
|
||||
email: z.string().email().optional()
|
||||
})
|
||||
.strict();
|
||||
email: z.email().optional()
|
||||
});
|
||||
|
||||
export type RequestTotpSecretBody = z.infer<typeof requestTotpSecretBody>;
|
||||
|
||||
|
||||
@@ -17,14 +17,12 @@ import ConfirmPasswordReset from "@server/emails/templates/NotifyResetPassword";
|
||||
import { sendEmail } from "@server/emails";
|
||||
import { passwordSchema } from "@server/auth/passwordSchema";
|
||||
|
||||
export const resetPasswordBody = z
|
||||
.object({
|
||||
email: z.string().toLowerCase().email(),
|
||||
export const resetPasswordBody = z.strictObject({
|
||||
email: z.email().toLowerCase(),
|
||||
token: z.string(), // reset secret code
|
||||
newPassword: passwordSchema,
|
||||
code: z.string().optional() // 2fa code
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type ResetPasswordBody = z.infer<typeof resetPasswordBody>;
|
||||
|
||||
|
||||
@@ -99,28 +99,28 @@ async function clearChallenge(sessionId: string) {
|
||||
await db.delete(webauthnChallenge).where(eq(webauthnChallenge.sessionId, sessionId));
|
||||
}
|
||||
|
||||
export const registerSecurityKeyBody = z.object({
|
||||
export const registerSecurityKeyBody = z.strictObject({
|
||||
name: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
code: z.string().optional()
|
||||
}).strict();
|
||||
});
|
||||
|
||||
export const verifyRegistrationBody = z.object({
|
||||
export const verifyRegistrationBody = z.strictObject({
|
||||
credential: z.any()
|
||||
}).strict();
|
||||
});
|
||||
|
||||
export const startAuthenticationBody = z.object({
|
||||
email: z.string().email().optional()
|
||||
}).strict();
|
||||
export const startAuthenticationBody = z.strictObject({
|
||||
email: z.email().optional()
|
||||
});
|
||||
|
||||
export const verifyAuthenticationBody = z.object({
|
||||
export const verifyAuthenticationBody = z.strictObject({
|
||||
credential: z.any()
|
||||
}).strict();
|
||||
});
|
||||
|
||||
export const deleteSecurityKeyBody = z.object({
|
||||
export const deleteSecurityKeyBody = z.strictObject({
|
||||
password: z.string().min(1),
|
||||
code: z.string().optional()
|
||||
}).strict();
|
||||
});
|
||||
|
||||
export async function startRegistration(
|
||||
req: Request,
|
||||
|
||||
@@ -13,8 +13,8 @@ import { eq, and } from "drizzle-orm";
|
||||
import { UserType } from "@server/types/UserTypes";
|
||||
import moment from "moment";
|
||||
|
||||
const bodySchema = z.object({
|
||||
email: z.string().toLowerCase().email(),
|
||||
export const bodySchema = z.object({
|
||||
email: z.email().toLowerCase(),
|
||||
password: passwordSchema,
|
||||
setupToken: z.string().min(1, "Setup token is required")
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ import { build } from "@server/build";
|
||||
import resend, { AudienceIds, moveEmailToAudience } from "#dynamic/lib/resend";
|
||||
|
||||
export const signupBodySchema = z.object({
|
||||
email: z.string().toLowerCase().email(),
|
||||
email: z.email().toLowerCase(),
|
||||
password: passwordSchema,
|
||||
inviteToken: z.string().optional(),
|
||||
inviteId: z.string().optional(),
|
||||
|
||||
@@ -8,11 +8,9 @@ import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
|
||||
const validateSetupTokenSchema = z
|
||||
.object({
|
||||
const validateSetupTokenSchema = z.strictObject({
|
||||
token: z.string().min(1, "Token is required")
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type ValidateSetupTokenResponse = {
|
||||
valid: boolean;
|
||||
|
||||
@@ -13,11 +13,9 @@ import logger from "@server/logger";
|
||||
import { freeLimitSet, limitsService } from "@server/lib/billing";
|
||||
import { build } from "@server/build";
|
||||
|
||||
export const verifyEmailBody = z
|
||||
.object({
|
||||
export const verifyEmailBody = z.strictObject({
|
||||
code: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type VerifyEmailBody = z.infer<typeof verifyEmailBody>;
|
||||
|
||||
|
||||
@@ -18,13 +18,11 @@ import { generateBackupCodes } from "@server/lib/totp";
|
||||
import { verifySession } from "@server/auth/sessions/verifySession";
|
||||
import { unauthorized } from "@server/auth/unauthorizedResponse";
|
||||
|
||||
export const verifyTotpBody = z
|
||||
.object({
|
||||
email: z.string().email().optional(),
|
||||
export const verifyTotpBody = z.strictObject({
|
||||
email: z.email().optional(),
|
||||
password: z.string().optional(),
|
||||
code: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type VerifyTotpBody = z.infer<typeof verifyTotpBody>;
|
||||
|
||||
|
||||
@@ -40,10 +40,10 @@ import { logRequestAudit } from "./logRequestAudit";
|
||||
import cache from "@server/lib/cache";
|
||||
|
||||
const verifyResourceSessionSchema = z.object({
|
||||
sessions: z.record(z.string()).optional(),
|
||||
headers: z.record(z.string()).optional(),
|
||||
query: z.record(z.string()).optional(),
|
||||
originalRequestURL: z.string().url(),
|
||||
sessions: z.record(z.string(), z.string()).optional(),
|
||||
headers: z.record(z.string(), z.string()).optional(),
|
||||
query: z.record(z.string(), z.string()).optional(),
|
||||
originalRequestURL: z.url(),
|
||||
scheme: z.string(),
|
||||
host: z.string(),
|
||||
path: z.string(),
|
||||
@@ -60,6 +60,7 @@ type BasicUserData = {
|
||||
username: string;
|
||||
email: string | null;
|
||||
name: string | null;
|
||||
role: string | null;
|
||||
};
|
||||
|
||||
export type VerifyUserResponse = {
|
||||
@@ -883,7 +884,8 @@ async function isUserAllowedToAccessResource(
|
||||
return {
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
name: user.name
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
}
|
||||
|
||||
@@ -896,7 +898,8 @@ async function isUserAllowedToAccessResource(
|
||||
return {
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
name: user.name
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,17 +8,13 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { applyBlueprint } from "@server/lib/blueprints/applyBlueprint";
|
||||
|
||||
const applyBlueprintSchema = z
|
||||
.object({
|
||||
const applyBlueprintSchema = z.strictObject({
|
||||
blueprint: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const applyBlueprintParamsSchema = z
|
||||
.object({
|
||||
const applyBlueprintParamsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "put",
|
||||
|
||||
@@ -12,15 +12,13 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { BlueprintData } from "./types";
|
||||
|
||||
const getBlueprintSchema = z
|
||||
.object({
|
||||
const getBlueprintSchema = z.strictObject({
|
||||
blueprintId: z
|
||||
.string()
|
||||
.transform(stoi)
|
||||
.pipe(z.number().int().positive()),
|
||||
.pipe(z.int().positive()),
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
async function query(blueprintId: number, orgId: string) {
|
||||
// Get the client
|
||||
|
||||
@@ -10,28 +10,24 @@ import { fromZodError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { BlueprintData } from "./types";
|
||||
|
||||
const listBluePrintsParamsSchema = z
|
||||
.object({
|
||||
const listBluePrintsParamsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const listBluePrintsSchema = z
|
||||
.object({
|
||||
const listBluePrintsSchema = z.strictObject({
|
||||
limit: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative()),
|
||||
.pipe(z.int().nonnegative()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
})
|
||||
.strict();
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
async function queryBlueprints(orgId: string, limit: number, offset: number) {
|
||||
const res = await db
|
||||
|
||||
@@ -25,23 +25,19 @@ import { listExitNodes } from "#dynamic/lib/exitNodes";
|
||||
import { generateId } from "@server/auth/sessions/app";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const createClientParamsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const createClientSchema = z.strictObject({
|
||||
name: z.string().min(1).max(255),
|
||||
olmId: z.string(),
|
||||
secret: z.string(),
|
||||
subnet: z.string(),
|
||||
type: z.enum(["olm"])
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type CreateClientBody = z.infer<typeof bodySchema>;
|
||||
export type CreateClientBody = z.infer<typeof createClientSchema>;
|
||||
|
||||
export type CreateClientResponse = Client;
|
||||
|
||||
@@ -51,11 +47,11 @@ registry.registerPath({
|
||||
description: "Create a new client for an organization.",
|
||||
tags: [OpenAPITags.Client, OpenAPITags.Org],
|
||||
request: {
|
||||
params: paramsSchema,
|
||||
params: createClientParamsSchema,
|
||||
body: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: bodySchema
|
||||
schema: createClientSchema
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +65,7 @@ export async function createClient(
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedBody = bodySchema.safeParse(req.body);
|
||||
const parsedBody = createClientSchema.safeParse(req.body);
|
||||
if (!parsedBody.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
@@ -81,7 +77,7 @@ export async function createClient(
|
||||
|
||||
const { name, type, olmId, secret, subnet } = parsedBody.data;
|
||||
|
||||
const parsedParams = paramsSchema.safeParse(req.params);
|
||||
const parsedParams = createClientParamsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
|
||||
@@ -10,11 +10,9 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const deleteClientSchema = z
|
||||
.object({
|
||||
clientId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||
})
|
||||
.strict();
|
||||
const deleteClientSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "delete",
|
||||
|
||||
@@ -11,11 +11,9 @@ import stoi from "@server/lib/stoi";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const getClientSchema = z
|
||||
.object({
|
||||
clientId: z.string().transform(stoi).pipe(z.number().int().positive())
|
||||
})
|
||||
.strict();
|
||||
const getClientSchema = z.strictObject({
|
||||
clientId: z.string().transform(stoi).pipe(z.int().positive())
|
||||
});
|
||||
|
||||
async function query(clientId: number) {
|
||||
// Get the client
|
||||
|
||||
@@ -78,11 +78,9 @@ async function getLatestOlmVersion(): Promise<string | null> {
|
||||
}
|
||||
|
||||
|
||||
const listClientsParamsSchema = z
|
||||
.object({
|
||||
const listClientsParamsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const listClientsSchema = z.object({
|
||||
limit: z
|
||||
@@ -90,13 +88,13 @@ const listClientsSchema = z.object({
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().positive()),
|
||||
.pipe(z.int().positive()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative()),
|
||||
.pipe(z.int().nonnegative()),
|
||||
filter: z
|
||||
.enum(["user", "machine"])
|
||||
.optional()
|
||||
|
||||
@@ -15,11 +15,9 @@ export type PickClientDefaultsResponse = {
|
||||
subnet: string;
|
||||
};
|
||||
|
||||
const pickClientDefaultsSchema = z
|
||||
.object({
|
||||
const pickClientDefaultsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { Client, db, exitNodes, sites } from "@server/db";
|
||||
import { Client, db, exitNodes, olms, sites } from "@server/db";
|
||||
import { clients, clientSites } from "@server/db";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -10,17 +10,13 @@ import { eq, and } from "drizzle-orm";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const updateClientParamsSchema = z
|
||||
.object({
|
||||
clientId: z.string().transform(Number).pipe(z.number().int().positive())
|
||||
})
|
||||
.strict();
|
||||
const updateClientParamsSchema = z.strictObject({
|
||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||
});
|
||||
|
||||
const updateClientSchema = z
|
||||
.object({
|
||||
name: z.string().min(1).max(255).optional()
|
||||
})
|
||||
.strict();
|
||||
const updateClientSchema = z.strictObject({
|
||||
name: z.string().min(1).max(255).optional()
|
||||
});
|
||||
|
||||
export type UpdateClientBody = z.infer<typeof updateClientSchema>;
|
||||
|
||||
|
||||
@@ -15,20 +15,16 @@ import { isSecondLevelDomain, isValidDomain } from "@server/lib/validators";
|
||||
import { build } from "@server/build";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
type: z.enum(["ns", "cname", "wildcard"]),
|
||||
baseDomain: subdomainSchema,
|
||||
certResolver: z.string().optional().nullable(),
|
||||
preferWildcardCert: z.boolean().optional().nullable() // optional, only for wildcard
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
|
||||
export type CreateDomainResponse = {
|
||||
|
||||
@@ -10,12 +10,10 @@ import { and, eq } from "drizzle-orm";
|
||||
import { usageService } from "@server/lib/billing/usageService";
|
||||
import { FeatureId } from "@server/lib/billing";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const paramsSchema = z.strictObject({
|
||||
domainId: z.string(),
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type DeleteAccountDomainResponse = {
|
||||
success: boolean;
|
||||
|
||||
@@ -10,12 +10,10 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { getServerIp } from "@server/lib/serverIpService"; // your in-memory IP module
|
||||
|
||||
const getDNSRecordsSchema = z
|
||||
.object({
|
||||
const getDNSRecordsSchema = z.strictObject({
|
||||
domainId: z.string(),
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
async function query(domainId: string) {
|
||||
const records = await db
|
||||
|
||||
@@ -10,14 +10,12 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { domain } from "zod/v4/core/regexes";
|
||||
|
||||
const getDomainSchema = z
|
||||
.object({
|
||||
const getDomainSchema = z.strictObject({
|
||||
domainId: z
|
||||
.string()
|
||||
.optional(),
|
||||
orgId: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
async function query(domainId?: string, orgId?: string) {
|
||||
if (domainId) {
|
||||
|
||||
@@ -10,28 +10,24 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const listDomainsParamsSchema = z
|
||||
.object({
|
||||
const listDomainsParamsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const listDomainsSchema = z
|
||||
.object({
|
||||
const listDomainsSchema = z.strictObject({
|
||||
limit: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative()),
|
||||
.pipe(z.int().nonnegative()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
})
|
||||
.strict();
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
async function queryDomains(orgId: string, limit: number, offset: number) {
|
||||
const res = await db
|
||||
|
||||
@@ -8,12 +8,10 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const paramsSchema = z.strictObject({
|
||||
domainId: z.string(),
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type RestartOrgDomainResponse = {
|
||||
success: boolean;
|
||||
|
||||
@@ -9,19 +9,15 @@ import { fromError } from "zod-validation-error";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
const paramsSchema = z.strictObject({
|
||||
orgId: z.string(),
|
||||
domainId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
certResolver: z.string().optional().nullable(),
|
||||
preferWildcardCert: z.boolean().optional().nullable()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type UpdateDomainResponse = {
|
||||
domainId: string;
|
||||
|
||||
@@ -181,6 +181,7 @@ authenticated.post(
|
||||
client.updateClient,
|
||||
);
|
||||
|
||||
|
||||
// authenticated.get(
|
||||
// "/site/:siteId/roles",
|
||||
// verifySiteAccess,
|
||||
@@ -194,6 +195,7 @@ authenticated.post(
|
||||
logActionAudit(ActionsEnum.updateSite),
|
||||
site.updateSite,
|
||||
);
|
||||
|
||||
authenticated.delete(
|
||||
"/site/:siteId",
|
||||
verifySiteAccess,
|
||||
|
||||
@@ -11,19 +11,15 @@ import config from "@server/lib/config";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { idp, idpOrg } from "@server/db";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
idpId: z.coerce.number(),
|
||||
const paramsSchema = z.strictObject({
|
||||
idpId: z.coerce.number<number>(),
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
roleMapping: z.string().optional(),
|
||||
orgMapping: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type CreateIdpOrgPolicyResponse = {};
|
||||
|
||||
|
||||
@@ -12,22 +12,20 @@ import { generateOidcRedirectUrl } from "@server/lib/idp/generateRedirectUrl";
|
||||
import { encrypt } from "@server/lib/crypto";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
const paramsSchema = z.object({}).strict();
|
||||
const paramsSchema = z.strictObject({});
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
name: z.string().nonempty(),
|
||||
clientId: z.string().nonempty(),
|
||||
clientSecret: z.string().nonempty(),
|
||||
authUrl: z.string().url(),
|
||||
tokenUrl: z.string().url(),
|
||||
authUrl: z.url(),
|
||||
tokenUrl: z.url(),
|
||||
identifierPath: z.string().nonempty(),
|
||||
emailPath: z.string().optional(),
|
||||
namePath: z.string().optional(),
|
||||
scopes: z.string().nonempty(),
|
||||
autoProvision: z.boolean().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type CreateIdpResponse = {
|
||||
idpId: number;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
orgId: z.string().optional(), // Optional; used with org idp in saas
|
||||
idpId: z.coerce.number()
|
||||
idpId: z.coerce.number<number>()
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
@@ -10,12 +10,10 @@ import { idp, idpOrg } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
idpId: z.coerce.number(),
|
||||
const paramsSchema = z.strictObject({
|
||||
idpId: z.coerce.number<number>(),
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
registry.registerPath({
|
||||
method: "delete",
|
||||
|
||||
@@ -19,15 +19,13 @@ import { TierId } from "@server/lib/billing/tiers";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
idpId: z.coerce.number()
|
||||
idpId: z.coerce.number<number>()
|
||||
})
|
||||
.strict();
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
redirectUrl: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const querySchema = z.object({
|
||||
orgId: z.string().optional() // check what actuall calls it
|
||||
|
||||
@@ -14,7 +14,7 @@ import { decrypt } from "@server/lib/crypto";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
idpId: z.coerce.number()
|
||||
idpId: z.coerce.number<number>()
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
@@ -11,25 +11,23 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const paramsSchema = z.object({
|
||||
idpId: z.coerce.number()
|
||||
idpId: z.coerce.number<number>()
|
||||
});
|
||||
|
||||
const querySchema = z
|
||||
.object({
|
||||
const querySchema = z.strictObject({
|
||||
limit: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative()),
|
||||
.pipe(z.int().nonnegative()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
})
|
||||
.strict();
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
async function query(idpId: number, limit: number, offset: number) {
|
||||
const res = await db
|
||||
|
||||
@@ -10,22 +10,20 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const querySchema = z
|
||||
.object({
|
||||
const querySchema = z.strictObject({
|
||||
limit: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative()),
|
||||
.pipe(z.int().nonnegative()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
})
|
||||
.strict();
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
async function query(limit: number, offset: number) {
|
||||
const res = await db
|
||||
|
||||
@@ -10,19 +10,15 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { idp, idpOrg } from "@server/db";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
idpId: z.coerce.number(),
|
||||
const paramsSchema = z.strictObject({
|
||||
idpId: z.coerce.number<number>(),
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
roleMapping: z.string().optional(),
|
||||
orgMapping: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type UpdateIdpOrgPolicyResponse = {};
|
||||
|
||||
|
||||
@@ -14,12 +14,11 @@ import config from "@server/lib/config";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
idpId: z.coerce.number()
|
||||
idpId: z.coerce.number<number>()
|
||||
})
|
||||
.strict();
|
||||
|
||||
const bodySchema = z
|
||||
.object({
|
||||
const bodySchema = z.strictObject({
|
||||
name: z.string().optional(),
|
||||
clientId: z.string().optional(),
|
||||
clientSecret: z.string().optional(),
|
||||
@@ -32,8 +31,7 @@ const bodySchema = z
|
||||
autoProvision: z.boolean().optional(),
|
||||
defaultRoleMapping: z.string().optional(),
|
||||
defaultOrgMapping: z.string().optional()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export type UpdateIdpResponse = {
|
||||
idpId: number;
|
||||
|
||||
@@ -41,7 +41,7 @@ const ensureTrailingSlash = (url: string): string => {
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
idpId: z.coerce.number()
|
||||
idpId: z.coerce.number<number>()
|
||||
})
|
||||
.strict();
|
||||
|
||||
@@ -52,7 +52,7 @@ const bodySchema = z.object({
|
||||
});
|
||||
|
||||
const querySchema = z.object({
|
||||
loginPageId: z.coerce.number().optional()
|
||||
loginPageId: z.coerce.number<number>().optional()
|
||||
});
|
||||
|
||||
export type ValidateOidcUrlCallbackResponse = {
|
||||
|
||||
@@ -23,12 +23,10 @@ export type CreateNewtResponse = {
|
||||
secret: string;
|
||||
};
|
||||
|
||||
const createNewtSchema = z
|
||||
.object({
|
||||
const createNewtSchema = z.strictObject({
|
||||
newtId: z.string(),
|
||||
secret: z.string()
|
||||
})
|
||||
.strict();
|
||||
});
|
||||
|
||||
export async function createNewt(
|
||||
req: Request,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user