mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-05 07:16:24 +00:00
Compare commits
12 Commits
crowdin_de
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9a509be53 | ||
|
|
7fa1180d10 | ||
|
|
769d36e289 | ||
|
|
a7a41b820e | ||
|
|
8b50f1fb65 | ||
|
|
2d78a4b628 | ||
|
|
527d4cc777 | ||
|
|
01361884eb | ||
|
|
6c4cbcab5d | ||
|
|
aac25f0a53 | ||
|
|
f617f93a94 | ||
|
|
51629247a5 |
@@ -38,7 +38,5 @@ flags:
|
|||||||
disable_user_create_org: false
|
disable_user_create_org: false
|
||||||
allow_raw_resources: true
|
allow_raw_resources: true
|
||||||
|
|
||||||
{{if .IsPostgreSQL}}
|
{{if .IsPostgreSQL}}postgres:
|
||||||
postgres:
|
connection_string: postgresql://pangolin:{{.IsPostgreSQLPass}}@postgres:5432/pangolin{{end}}
|
||||||
connection_string: postgresql://pangolin:{{.IsPostgreSQLPass}}@postgres:5432/pangolin
|
|
||||||
{{end}}
|
|
||||||
|
|||||||
@@ -7,23 +7,17 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 1g
|
memory: 2g
|
||||||
reservations:
|
reservations:
|
||||||
memory: 256m
|
memory: 512m
|
||||||
{{if or .IsPostgreSQL .IsRedis}}
|
{{if or .IsPostgreSQL .IsRedis}}depends_on:
|
||||||
depends_on:
|
{{if .IsPostgreSQL}}postgres:
|
||||||
{{if .IsPostgreSQL}}
|
condition: service_healthy{{end}}
|
||||||
postgres:
|
{{if .IsRedis}}redis:
|
||||||
condition: service_healthy
|
condition: service_healthy{{end}}
|
||||||
{{end}}
|
|
||||||
{{if .IsRedis}}
|
|
||||||
redis:
|
|
||||||
condition: service_healthy
|
|
||||||
{{end}}
|
|
||||||
networks:
|
networks:
|
||||||
- default
|
- default
|
||||||
- backend
|
- backend{{end}}
|
||||||
{{end}}
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./config:/app/config
|
- ./config:/app/config
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -31,8 +25,8 @@ services:
|
|||||||
interval: "10s"
|
interval: "10s"
|
||||||
timeout: "10s"
|
timeout: "10s"
|
||||||
retries: 15
|
retries: 15
|
||||||
{{if .InstallGerbil}}
|
|
||||||
gerbil:
|
{{if .InstallGerbil}}gerbil:
|
||||||
image: docker.io/fosrl/gerbil:{{.GerbilVersion}}
|
image: docker.io/fosrl/gerbil:{{.GerbilVersion}}
|
||||||
container_name: gerbil
|
container_name: gerbil
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -53,8 +47,8 @@ services:
|
|||||||
- 21820:21820/udp
|
- 21820:21820/udp
|
||||||
- 443:443
|
- 443:443
|
||||||
- 443:443/udp # For http3 QUIC if desired
|
- 443:443/udp # For http3 QUIC if desired
|
||||||
- 80:80
|
- 80:80{{end}}
|
||||||
{{end}}
|
|
||||||
traefik:
|
traefik:
|
||||||
image: docker.io/traefik:v3.6
|
image: docker.io/traefik:v3.6
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
@@ -62,8 +56,7 @@ services:
|
|||||||
{{if .InstallGerbil}}network_mode: service:gerbil # Ports appear on the gerbil service{{end}}{{if not .InstallGerbil}}
|
{{if .InstallGerbil}}network_mode: service:gerbil # Ports appear on the gerbil service{{end}}{{if not .InstallGerbil}}
|
||||||
ports:
|
ports:
|
||||||
- 443:443
|
- 443:443
|
||||||
- 80:80
|
- 80:80{{end}}
|
||||||
{{end}}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
pangolin:
|
pangolin:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -74,8 +67,7 @@ services:
|
|||||||
- ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
|
- ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
|
||||||
- ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs
|
- ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs
|
||||||
|
|
||||||
{{if .IsPostgreSQL}}
|
{{if .IsPostgreSQL}}postgres:
|
||||||
postgres:
|
|
||||||
image: postgres:18
|
image: postgres:18
|
||||||
container_name: postgres
|
container_name: postgres
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -91,11 +83,9 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
networks:
|
networks:
|
||||||
- backend
|
- backend{{end}}
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if .IsRedis}}
|
{{if .IsRedis}}redis:
|
||||||
redis:
|
|
||||||
image: redis:8-trixie
|
image: redis:8-trixie
|
||||||
container_name: redis
|
container_name: redis
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -113,17 +103,14 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
networks:
|
networks:
|
||||||
- backend
|
- backend{{end}}
|
||||||
{{end}}
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
name: pangolin_frontend
|
name: pangolin_frontend
|
||||||
{{if .EnableIPv6}} enable_ipv6: true{{end}}
|
{{if .EnableIPv6}} enable_ipv6: true{{end}}
|
||||||
{{if or .IsPostgreSQL .IsRedis}}
|
{{if or .IsPostgreSQL .IsRedis}} backend:
|
||||||
backend:
|
|
||||||
driver: bridge
|
driver: bridge
|
||||||
name: pangolin_backend
|
name: pangolin_backend
|
||||||
internal: true
|
internal: true{{end}}
|
||||||
{{end}}
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
{{if .IsRedis}}
|
{{if .IsRedis}}redis:
|
||||||
redis:
|
|
||||||
host: "redis"
|
host: "redis"
|
||||||
port: 6379
|
port: 6379
|
||||||
password: "{{.IsRedisPass}}"
|
password: "{{.IsRedisPass}}"{{end}}
|
||||||
{{end}}
|
|
||||||
|
|||||||
@@ -71,9 +71,12 @@ const (
|
|||||||
Undefined SupportedContainer = "undefined"
|
Undefined SupportedContainer = "undefined"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var redisFlag *bool
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
crowdsecFlag := flag.Bool("crowdsec", false, "Enable the CrowdSec installation prompt")
|
crowdsecFlag := flag.Bool("crowdsec", false, "Enable the CrowdSec installation prompt")
|
||||||
|
redisFlag = flag.Bool("redis", false, "Install Redis as cacheing solution. Required for HA. Not required for the Enterprise version.")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// print a banner about prerequisites - opening port 80, 443, 51820, and 21820 on the VPS and firewall and pointing your domain to the VPS IP with a records. Docs are at http://localhost:3000/Getting%20Started/dns-networking
|
// print a banner about prerequisites - opening port 80, 443, 51820, and 21820 on the VPS and firewall and pointing your domain to the VPS IP with a records. Docs are at http://localhost:3000/Getting%20Started/dns-networking
|
||||||
@@ -491,13 +494,13 @@ func collectUserInput() Config {
|
|||||||
|
|
||||||
config.IsEnterprise = readBoolNoDefault("Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.")
|
config.IsEnterprise = readBoolNoDefault("Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.")
|
||||||
if config.IsEnterprise {
|
if config.IsEnterprise {
|
||||||
config.IsRedis = readBool("Do you want to run the Redis containers locally? Required for HA.")
|
if *redisFlag {
|
||||||
if config.IsRedis {
|
config.IsRedis = true
|
||||||
config.IsRedisPass = readPassword("Enter a unique password for the Redis service.")
|
config.IsRedisPass = readPassword("Enter a unique password for the Redis service.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.IsPostgreSQL = readBool("Do you want to run the PostgreSQL containers locally? Otherwise, default to the local SQLite database only.", false)
|
config.IsPostgreSQL = readBool("Do you want to use PostgreSQL (not recommended for most users)?", false)
|
||||||
if config.IsPostgreSQL {
|
if config.IsPostgreSQL {
|
||||||
config.IsPostgreSQLPass = readPassword("Enter a unique password for the PostgreSQL pangolin user.")
|
config.IsPostgreSQLPass = readPassword("Enter a unique password for the PostgreSQL pangolin user.")
|
||||||
}
|
}
|
||||||
@@ -544,7 +547,7 @@ func collectUserInput() Config {
|
|||||||
fmt.Println("\n=== Advanced Configuration ===")
|
fmt.Println("\n=== Advanced Configuration ===")
|
||||||
|
|
||||||
config.EnableIPv6 = readBool("Is your server IPv6 capable?", true)
|
config.EnableIPv6 = readBool("Is your server IPv6 capable?", true)
|
||||||
config.EnableMaxMind = readBool("Do you want to download the MaxMind GeoLite2 Country and ADN databases for blocking functionality?", true)
|
config.EnableMaxMind = readBool("Do you want to download the MaxMind GeoLite2 Country and ASN databases for blocking functionality?", true)
|
||||||
|
|
||||||
if config.DashboardDomain == "" {
|
if config.DashboardDomain == "" {
|
||||||
fmt.Println("Error: Dashboard Domain name is required")
|
fmt.Println("Error: Dashboard Domain name is required")
|
||||||
|
|||||||
@@ -2046,6 +2046,7 @@
|
|||||||
"requireDeviceApproval": "Require Device Approvals",
|
"requireDeviceApproval": "Require Device Approvals",
|
||||||
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and access resources.",
|
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and access resources.",
|
||||||
"sshSettings": "SSH Settings",
|
"sshSettings": "SSH Settings",
|
||||||
|
"sshAccess": "SSH Access",
|
||||||
"rdpSettings": "RDP Settings",
|
"rdpSettings": "RDP Settings",
|
||||||
"vncSettings": "VNC Settings",
|
"vncSettings": "VNC Settings",
|
||||||
"sshServer": "SSH Server",
|
"sshServer": "SSH Server",
|
||||||
|
|||||||
344
package-lock.json
generated
344
package-lock.json
generated
@@ -144,16 +144,16 @@
|
|||||||
"drizzle-kit": "0.31.10",
|
"drizzle-kit": "0.31.10",
|
||||||
"esbuild": "0.28.0",
|
"esbuild": "0.28.0",
|
||||||
"esbuild-node-externals": "1.22.0",
|
"esbuild-node-externals": "1.22.0",
|
||||||
"eslint": "10.4.0",
|
"eslint": "10.4.1",
|
||||||
"eslint-config-next": "16.2.6",
|
"eslint-config-next": "16.2.7",
|
||||||
"postcss": "8.5.15",
|
"postcss": "8.5.15",
|
||||||
"prettier": "3.8.3",
|
"prettier": "3.8.3",
|
||||||
"react-email": "6.5.0",
|
"react-email": "6.5.0",
|
||||||
"tailwindcss": "4.3.0",
|
"tailwindcss": "4.3.0",
|
||||||
"tsc-alias": "1.8.17",
|
"tsc-alias": "1.8.17",
|
||||||
"tsx": "4.22.3",
|
"tsx": "4.22.4",
|
||||||
"typescript": "6.0.3",
|
"typescript": "6.0.3",
|
||||||
"typescript-eslint": "8.60.0"
|
"typescript-eslint": "8.60.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
@@ -1783,9 +1783,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit": {
|
"node_modules/@eslint/plugin-kit": {
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz",
|
||||||
"integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==",
|
"integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2076,9 +2076,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2095,9 +2092,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2114,9 +2108,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2133,9 +2124,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2152,9 +2140,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2187,9 +2172,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2222,9 +2204,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2247,9 +2226,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2272,9 +2248,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2297,9 +2270,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2322,9 +2292,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2369,9 +2336,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2616,9 +2580,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
"version": "16.2.6",
|
"version": "16.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.7.tgz",
|
||||||
"integrity": "sha512-Z8l6o4JWKUl755x4R+wogD86KPeU+Ckw4K+SYG4kHeOJtRenDeK+OSbGcqZpDtbwn9DsJVdir2UxmwXuinUbUw==",
|
"integrity": "sha512-VbS+QgMHqvIDMTIqD2xMBKK1otIpdAUKA8VLHFwR9h6OfU/mOm7w/69nQcvdmI8hCk99Wr2AsGLn/PJ/tMHw1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2664,9 +2628,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2683,9 +2644,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2941,9 +2899,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2960,9 +2915,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3200,9 +3152,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3219,9 +3168,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3582,9 +3528,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3605,9 +3548,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3628,9 +3568,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3651,9 +3588,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6873,9 +6807,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6892,9 +6823,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6911,9 +6839,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -6930,9 +6855,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7200,9 +7122,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7220,9 +7139,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7296,6 +7212,72 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.2.1",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tybys/wasm-util": "^0.10.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emnapi/core": "^1.7.1",
|
||||||
|
"@emnapi/runtime": "^1.7.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||||
|
"version": "0.10.1",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"dev": true,
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "0BSD",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz",
|
||||||
@@ -8113,17 +8095,17 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz",
|
||||||
"integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==",
|
"integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.12.2",
|
"@eslint-community/regexpp": "^4.12.2",
|
||||||
"@typescript-eslint/scope-manager": "8.60.0",
|
"@typescript-eslint/scope-manager": "8.60.1",
|
||||||
"@typescript-eslint/type-utils": "8.60.0",
|
"@typescript-eslint/type-utils": "8.60.1",
|
||||||
"@typescript-eslint/utils": "8.60.0",
|
"@typescript-eslint/utils": "8.60.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.60.0",
|
"@typescript-eslint/visitor-keys": "8.60.1",
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"ts-api-utils": "^2.5.0"
|
"ts-api-utils": "^2.5.0"
|
||||||
@@ -8136,7 +8118,7 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^8.60.0",
|
"@typescript-eslint/parser": "^8.60.1",
|
||||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||||
"typescript": ">=4.8.4 <6.1.0"
|
"typescript": ">=4.8.4 <6.1.0"
|
||||||
}
|
}
|
||||||
@@ -8152,16 +8134,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz",
|
||||||
"integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==",
|
"integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.60.0",
|
"@typescript-eslint/scope-manager": "8.60.1",
|
||||||
"@typescript-eslint/types": "8.60.0",
|
"@typescript-eslint/types": "8.60.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.60.0",
|
"@typescript-eslint/typescript-estree": "8.60.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.60.0",
|
"@typescript-eslint/visitor-keys": "8.60.1",
|
||||||
"debug": "^4.4.3"
|
"debug": "^4.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -8177,14 +8159,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service": {
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz",
|
||||||
"integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==",
|
"integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.60.0",
|
"@typescript-eslint/tsconfig-utils": "^8.60.1",
|
||||||
"@typescript-eslint/types": "^8.60.0",
|
"@typescript-eslint/types": "^8.60.1",
|
||||||
"debug": "^4.4.3"
|
"debug": "^4.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -8199,14 +8181,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz",
|
||||||
"integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==",
|
"integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.60.0",
|
"@typescript-eslint/types": "8.60.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.60.0"
|
"@typescript-eslint/visitor-keys": "8.60.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -8217,9 +8199,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz",
|
||||||
"integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==",
|
"integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -8234,15 +8216,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz",
|
||||||
"integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==",
|
"integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.60.0",
|
"@typescript-eslint/types": "8.60.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.60.0",
|
"@typescript-eslint/typescript-estree": "8.60.1",
|
||||||
"@typescript-eslint/utils": "8.60.0",
|
"@typescript-eslint/utils": "8.60.1",
|
||||||
"debug": "^4.4.3",
|
"debug": "^4.4.3",
|
||||||
"ts-api-utils": "^2.5.0"
|
"ts-api-utils": "^2.5.0"
|
||||||
},
|
},
|
||||||
@@ -8259,9 +8241,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz",
|
||||||
"integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==",
|
"integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -8273,16 +8255,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz",
|
||||||
"integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==",
|
"integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.60.0",
|
"@typescript-eslint/project-service": "8.60.1",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.60.0",
|
"@typescript-eslint/tsconfig-utils": "8.60.1",
|
||||||
"@typescript-eslint/types": "8.60.0",
|
"@typescript-eslint/types": "8.60.1",
|
||||||
"@typescript-eslint/visitor-keys": "8.60.0",
|
"@typescript-eslint/visitor-keys": "8.60.1",
|
||||||
"debug": "^4.4.3",
|
"debug": "^4.4.3",
|
||||||
"minimatch": "^10.2.2",
|
"minimatch": "^10.2.2",
|
||||||
"semver": "^7.7.3",
|
"semver": "^7.7.3",
|
||||||
@@ -8301,16 +8283,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz",
|
||||||
"integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==",
|
"integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.9.1",
|
"@eslint-community/eslint-utils": "^4.9.1",
|
||||||
"@typescript-eslint/scope-manager": "8.60.0",
|
"@typescript-eslint/scope-manager": "8.60.1",
|
||||||
"@typescript-eslint/types": "8.60.0",
|
"@typescript-eslint/types": "8.60.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.60.0"
|
"@typescript-eslint/typescript-estree": "8.60.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -8325,13 +8307,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz",
|
||||||
"integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==",
|
"integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.60.0",
|
"@typescript-eslint/types": "8.60.1",
|
||||||
"eslint-visitor-keys": "^5.0.0"
|
"eslint-visitor-keys": "^5.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -8448,9 +8430,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8465,9 +8444,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8482,9 +8458,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8499,9 +8472,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8516,9 +8486,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8533,9 +8500,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -11347,9 +11311,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "10.4.0",
|
"version": "10.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz",
|
||||||
"integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==",
|
"integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -11358,7 +11322,7 @@
|
|||||||
"@eslint/config-array": "^0.23.5",
|
"@eslint/config-array": "^0.23.5",
|
||||||
"@eslint/config-helpers": "^0.6.0",
|
"@eslint/config-helpers": "^0.6.0",
|
||||||
"@eslint/core": "^1.2.1",
|
"@eslint/core": "^1.2.1",
|
||||||
"@eslint/plugin-kit": "^0.7.1",
|
"@eslint/plugin-kit": "^0.7.2",
|
||||||
"@humanfs/node": "^0.16.6",
|
"@humanfs/node": "^0.16.6",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.4.2",
|
"@humanwhocodes/retry": "^0.4.2",
|
||||||
@@ -11403,13 +11367,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-config-next": {
|
"node_modules/eslint-config-next": {
|
||||||
"version": "16.2.6",
|
"version": "16.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.7.tgz",
|
||||||
"integrity": "sha512-z2ELYSkyrrJ6cuunTU8vhsT/RpouPkjaSah06nVW6Rg2Hpg0Vs8s497/e5s8G8qtdp4ccsiovz5P1rv+5VSW2Q==",
|
"integrity": "sha512-CQ2aNXkrsjaGA2oJBE1LYnlRdphIAQE9ZQfX9hSv1PNGPyiOMSaVeBfTIO29QxYz+ij/hZudK0cfpCG1HXWstg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/eslint-plugin-next": "16.2.6",
|
"@next/eslint-plugin-next": "16.2.7",
|
||||||
"eslint-import-resolver-node": "^0.3.6",
|
"eslint-import-resolver-node": "^0.3.6",
|
||||||
"eslint-import-resolver-typescript": "^3.5.2",
|
"eslint-import-resolver-typescript": "^3.5.2",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
@@ -13767,9 +13731,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -13791,9 +13752,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -15001,9 +14959,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -15020,9 +14975,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -17658,9 +17610,9 @@
|
|||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/tsx": {
|
"node_modules/tsx": {
|
||||||
"version": "4.22.3",
|
"version": "4.22.4",
|
||||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz",
|
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz",
|
||||||
"integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==",
|
"integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -17874,16 +17826,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.60.0",
|
"version": "8.60.1",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz",
|
||||||
"integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==",
|
"integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.60.0",
|
"@typescript-eslint/eslint-plugin": "8.60.1",
|
||||||
"@typescript-eslint/parser": "8.60.0",
|
"@typescript-eslint/parser": "8.60.1",
|
||||||
"@typescript-eslint/typescript-estree": "8.60.0",
|
"@typescript-eslint/typescript-estree": "8.60.1",
|
||||||
"@typescript-eslint/utils": "8.60.0"
|
"@typescript-eslint/utils": "8.60.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|||||||
@@ -167,16 +167,16 @@
|
|||||||
"drizzle-kit": "0.31.10",
|
"drizzle-kit": "0.31.10",
|
||||||
"esbuild": "0.28.0",
|
"esbuild": "0.28.0",
|
||||||
"esbuild-node-externals": "1.22.0",
|
"esbuild-node-externals": "1.22.0",
|
||||||
"eslint": "10.4.0",
|
"eslint": "10.4.1",
|
||||||
"eslint-config-next": "16.2.6",
|
"eslint-config-next": "16.2.7",
|
||||||
"postcss": "8.5.15",
|
"postcss": "8.5.15",
|
||||||
"prettier": "3.8.3",
|
"prettier": "3.8.3",
|
||||||
"react-email": "6.5.0",
|
"react-email": "6.5.0",
|
||||||
"tailwindcss": "4.3.0",
|
"tailwindcss": "4.3.0",
|
||||||
"tsc-alias": "1.8.17",
|
"tsc-alias": "1.8.17",
|
||||||
"tsx": "4.22.3",
|
"tsx": "4.22.4",
|
||||||
"typescript": "6.0.3",
|
"typescript": "6.0.3",
|
||||||
"typescript-eslint": "8.60.0"
|
"typescript-eslint": "8.60.1"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"esbuild": "0.28.0",
|
"esbuild": "0.28.0",
|
||||||
|
|||||||
@@ -665,7 +665,7 @@ export async function generateSubnetProxyTargetV2(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let targets: SubnetProxyTargetV2[] = [];
|
const targets: SubnetProxyTargetV2[] = [];
|
||||||
|
|
||||||
const portRange = [
|
const portRange = [
|
||||||
...parsePortRangeString(siteResource.tcpPortRangeString, "tcp"),
|
...parsePortRangeString(siteResource.tcpPortRangeString, "tcp"),
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export async function getTraefikConfig(
|
|||||||
filterOutNamespaceDomains = false, // UNUSED BUT USED IN PRIVATE
|
filterOutNamespaceDomains = false, // UNUSED BUT USED IN PRIVATE
|
||||||
generateLoginPageRouters = false, // UNUSED BUT USED IN PRIVATE
|
generateLoginPageRouters = false, // UNUSED BUT USED IN PRIVATE
|
||||||
allowRawResources = true,
|
allowRawResources = true,
|
||||||
allowMaintenancePage = true // UNUSED BUT USED IN PRIVATE
|
allowMaintenancePage = true, // UNUSED BUT USED IN PRIVATE
|
||||||
|
allowBrowserGatewayResources = true
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
// Get resources with their targets and sites in a single optimized query
|
// Get resources with their targets and sites in a single optimized query
|
||||||
// Start from sites on this exit node, then join to targets and resources
|
// Start from sites on this exit node, then join to targets and resources
|
||||||
@@ -240,7 +241,7 @@ export async function getTraefikConfig(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resource.http) {
|
if (resource.mode === "http") {
|
||||||
if (!resource.domainId || !resource.fullDomain) {
|
if (!resource.domainId || !resource.fullDomain) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -572,7 +573,7 @@ export async function getTraefikConfig(
|
|||||||
serviceName
|
serviceName
|
||||||
].loadBalancer.serversTransport = transportName;
|
].loadBalancer.serversTransport = transportName;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (resource.mode === "tcp" || resource.mode === "udp") {
|
||||||
// Non-HTTP (TCP/UDP) configuration
|
// Non-HTTP (TCP/UDP) configuration
|
||||||
if (!resource.enableProxy || !resource.proxyPort) {
|
if (!resource.enableProxy || !resource.proxyPort) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
322
server/middlewares/verifySiteAccess.test.ts
Normal file
322
server/middlewares/verifySiteAccess.test.ts
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
import { assertEquals } from "@test/assert";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for the cross-organization site binding prevention in verifySiteAccess.
|
||||||
|
*
|
||||||
|
* verifySiteAccess now includes a check: if req.userOrgId is already set by a
|
||||||
|
* previous middleware (e.g. verifyResourceAccess or verifyTargetAccess), and the
|
||||||
|
* loaded site's orgId differs from req.userOrgId, the request is rejected with
|
||||||
|
* 403 Forbidden.
|
||||||
|
*
|
||||||
|
* Route stacks after fix:
|
||||||
|
* PUT /resource/:resourceId/target
|
||||||
|
* → verifyResourceAccess → verifySiteAccess → verifyLimits → ...
|
||||||
|
* POST /target/:targetId
|
||||||
|
* → verifyTargetAccess → verifySiteAccess → verifyLimits → ...
|
||||||
|
*
|
||||||
|
* verifyResourceAccess sets req.userOrgId to the resource's org.
|
||||||
|
* verifyTargetAccess sets req.userOrgId to the target's resource org.
|
||||||
|
* verifySiteAccess then checks site.orgId against req.userOrgId before
|
||||||
|
* overwriting it with the site's org.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// --- Core org-matching logic (mirrors the check in verifySiteAccess) ---
|
||||||
|
function siteOrgMatchesExpectedOrg(
|
||||||
|
siteOrgId: string | null | undefined,
|
||||||
|
expectedOrgId: string | null | undefined
|
||||||
|
): boolean {
|
||||||
|
if (!siteOrgId || !expectedOrgId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return siteOrgId === expectedOrgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulates the condition check in verifySiteAccess:
|
||||||
|
// if (req.userOrgId && site.orgId !== req.userOrgId) { reject }
|
||||||
|
function shouldRejectCrossOrgSite(
|
||||||
|
siteOrgId: string,
|
||||||
|
reqUserOrgId: string | undefined
|
||||||
|
): boolean {
|
||||||
|
// The actual check in verifySiteAccess is:
|
||||||
|
// if (req.userOrgId && site.orgId !== req.userOrgId) { reject }
|
||||||
|
return !!(reqUserOrgId && siteOrgId !== reqUserOrgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Tests ---
|
||||||
|
|
||||||
|
function testSiteOrgMatchLogic() {
|
||||||
|
console.log("Running verifySiteAccess org-match logic tests...");
|
||||||
|
|
||||||
|
// Test 1: Same org — should match
|
||||||
|
{
|
||||||
|
const result = siteOrgMatchesExpectedOrg(
|
||||||
|
"org-attacker",
|
||||||
|
"org-attacker"
|
||||||
|
);
|
||||||
|
assertEquals(result, true, "Same org should match");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Different org — should NOT match (cross-org bypass scenario)
|
||||||
|
{
|
||||||
|
const result = siteOrgMatchesExpectedOrg("org-victim", "org-attacker");
|
||||||
|
assertEquals(
|
||||||
|
result,
|
||||||
|
false,
|
||||||
|
"Cross-org site should NOT match expected org"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Site orgId is null — should NOT match
|
||||||
|
{
|
||||||
|
const result = siteOrgMatchesExpectedOrg(null, "org-attacker");
|
||||||
|
assertEquals(result, false, "Null site orgId should NOT match");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Expected orgId is null — should NOT match
|
||||||
|
{
|
||||||
|
const result = siteOrgMatchesExpectedOrg("org-attacker", null);
|
||||||
|
assertEquals(result, false, "Null expected orgId should NOT match");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: Both null — should NOT match
|
||||||
|
{
|
||||||
|
const result = siteOrgMatchesExpectedOrg(null, null);
|
||||||
|
assertEquals(result, false, "Both null should NOT match");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: Empty string orgIds — should NOT match (empty string is falsy)
|
||||||
|
{
|
||||||
|
const result = siteOrgMatchesExpectedOrg("", "org-attacker");
|
||||||
|
assertEquals(result, false, "Empty site orgId should NOT match");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: Undefined orgIds — should NOT match
|
||||||
|
{
|
||||||
|
const result = siteOrgMatchesExpectedOrg(undefined, "org-attacker");
|
||||||
|
assertEquals(result, false, "Undefined site orgId should NOT match");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("All verifySiteAccess org-match logic tests passed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function testShouldRejectCrossOrgSite() {
|
||||||
|
console.log(
|
||||||
|
"Running shouldRejectCrossOrgSite tests (mirrors verifySiteAccess check)..."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test: No prior org context (undefined) — should NOT reject
|
||||||
|
// This is the normal case for site-only routes (e.g. PUT /site/:siteId)
|
||||||
|
// where verifySiteAccess runs without a prior verifyResourceAccess.
|
||||||
|
{
|
||||||
|
const shouldReject = shouldRejectCrossOrgSite("org-victim", undefined);
|
||||||
|
assertEquals(
|
||||||
|
shouldReject,
|
||||||
|
false,
|
||||||
|
"No prior org context should NOT reject (normal site routes)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Same org — should NOT reject
|
||||||
|
{
|
||||||
|
const shouldReject = shouldRejectCrossOrgSite(
|
||||||
|
"org-attacker",
|
||||||
|
"org-attacker"
|
||||||
|
);
|
||||||
|
assertEquals(shouldReject, false, "Same org should NOT reject");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Different org — should reject
|
||||||
|
{
|
||||||
|
const shouldReject = shouldRejectCrossOrgSite(
|
||||||
|
"org-victim",
|
||||||
|
"org-attacker"
|
||||||
|
);
|
||||||
|
assertEquals(shouldReject, true, "Cross-org site should be rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Empty string userOrgId — should NOT reject (falsy, check is skipped)
|
||||||
|
{
|
||||||
|
const shouldReject = shouldRejectCrossOrgSite("org-victim", "");
|
||||||
|
assertEquals(
|
||||||
|
shouldReject,
|
||||||
|
false,
|
||||||
|
"Empty string userOrgId should NOT reject (check is skipped)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("All shouldRejectCrossOrgSite tests passed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Route stack validation tests ---
|
||||||
|
|
||||||
|
function testRouteStackOrdering() {
|
||||||
|
console.log("Running route stack ordering tests...");
|
||||||
|
|
||||||
|
const createTargetStack = [
|
||||||
|
"verifyResourceAccess",
|
||||||
|
"verifySiteAccess",
|
||||||
|
"verifyLimits",
|
||||||
|
"verifyUserHasAction",
|
||||||
|
"logActionAudit",
|
||||||
|
"createTarget"
|
||||||
|
];
|
||||||
|
|
||||||
|
const updateTargetStack = [
|
||||||
|
"verifyTargetAccess",
|
||||||
|
"verifySiteAccess",
|
||||||
|
"verifyLimits",
|
||||||
|
"verifyUserHasAction",
|
||||||
|
"logActionAudit",
|
||||||
|
"updateTarget"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Verify verifySiteAccess comes after resource/target access middleware
|
||||||
|
{
|
||||||
|
const siteAccessIndex = createTargetStack.indexOf("verifySiteAccess");
|
||||||
|
const resourceAccessIndex = createTargetStack.indexOf(
|
||||||
|
"verifyResourceAccess"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
siteAccessIndex > resourceAccessIndex,
|
||||||
|
true,
|
||||||
|
"verifySiteAccess must come after verifyResourceAccess in create target stack"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const siteAccessIndex = updateTargetStack.indexOf("verifySiteAccess");
|
||||||
|
const targetAccessIndex =
|
||||||
|
updateTargetStack.indexOf("verifyTargetAccess");
|
||||||
|
assertEquals(
|
||||||
|
siteAccessIndex > targetAccessIndex,
|
||||||
|
true,
|
||||||
|
"verifySiteAccess must come after verifyTargetAccess in update target stack"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify verifySiteAccess comes before the handler
|
||||||
|
{
|
||||||
|
const siteAccessIndex = createTargetStack.indexOf("verifySiteAccess");
|
||||||
|
const handlerIndex = createTargetStack.indexOf("createTarget");
|
||||||
|
assertEquals(
|
||||||
|
siteAccessIndex < handlerIndex,
|
||||||
|
true,
|
||||||
|
"verifySiteAccess must come before createTarget handler"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const siteAccessIndex = updateTargetStack.indexOf("verifySiteAccess");
|
||||||
|
const handlerIndex = updateTargetStack.indexOf("updateTarget");
|
||||||
|
assertEquals(
|
||||||
|
siteAccessIndex < handlerIndex,
|
||||||
|
true,
|
||||||
|
"verifySiteAccess must come before updateTarget handler"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("All route stack ordering tests passed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Security scenario tests ---
|
||||||
|
|
||||||
|
function testSecurityScenarios() {
|
||||||
|
console.log("Running security scenario tests...");
|
||||||
|
|
||||||
|
// Scenario 1: Attacker has resource access in org_attacker, but tries to
|
||||||
|
// bind target to a site in org_victim.
|
||||||
|
// verifyResourceAccess passes (sets req.userOrgId = "org_attacker").
|
||||||
|
// verifySiteAccess loads site (org_victim), checks site.orgId !== req.userOrgId.
|
||||||
|
// Expected: 403 Forbidden.
|
||||||
|
{
|
||||||
|
const shouldReject = shouldRejectCrossOrgSite(
|
||||||
|
"org_victim",
|
||||||
|
"org_attacker"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
shouldReject,
|
||||||
|
true,
|
||||||
|
"Scenario 1: Cross-org site binding must be rejected"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 2: Attacker has resource access AND site access in another org.
|
||||||
|
// Even though the user has site access, verifySiteAccess rejects because
|
||||||
|
// the org-match check runs before the site access check.
|
||||||
|
// Expected: 403 Forbidden (org mismatch caught before site access check).
|
||||||
|
{
|
||||||
|
const shouldReject = shouldRejectCrossOrgSite(
|
||||||
|
"org_victim",
|
||||||
|
"org_attacker"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
shouldReject,
|
||||||
|
true,
|
||||||
|
"Scenario 2: Cross-org site must be rejected even if user has site access"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 3: Legitimate user creates target with site in same org.
|
||||||
|
// verifyResourceAccess passes, verifySiteAccess org-match passes (same org),
|
||||||
|
// verifySiteAccess site access passes.
|
||||||
|
// Expected: 201 Created.
|
||||||
|
{
|
||||||
|
const shouldReject = shouldRejectCrossOrgSite(
|
||||||
|
"org_attacker",
|
||||||
|
"org_attacker"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
shouldReject,
|
||||||
|
false,
|
||||||
|
"Scenario 3: Same-org site must be allowed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 4: WireGuard site in victim org — org mismatch is caught before
|
||||||
|
// any DB write, pickPort, addPeer, or addTargets side effect.
|
||||||
|
{
|
||||||
|
const shouldReject = shouldRejectCrossOrgSite(
|
||||||
|
"org_victim",
|
||||||
|
"org_attacker"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
shouldReject,
|
||||||
|
true,
|
||||||
|
"Scenario 4: WireGuard cross-org site must be rejected before addPeer"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 5: Newt site in victim org — same as scenario 4 but for newt.
|
||||||
|
{
|
||||||
|
const shouldReject = shouldRejectCrossOrgSite(
|
||||||
|
"org_victim",
|
||||||
|
"org_attacker"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
shouldReject,
|
||||||
|
true,
|
||||||
|
"Scenario 5: Newt cross-org site must be rejected before addTargets"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 6: Normal site-only route (e.g. PUT /site/:siteId) where
|
||||||
|
// verifySiteAccess runs without a prior verifyResourceAccess.
|
||||||
|
// req.userOrgId is undefined, so the org-match check is skipped.
|
||||||
|
// Normal site access verification proceeds.
|
||||||
|
{
|
||||||
|
const shouldReject = shouldRejectCrossOrgSite("org_victim", undefined);
|
||||||
|
assertEquals(
|
||||||
|
shouldReject,
|
||||||
|
false,
|
||||||
|
"Scenario 6: Site-only routes should skip org-match check"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("All security scenario tests passed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all tests
|
||||||
|
testSiteOrgMatchLogic();
|
||||||
|
testShouldRejectCrossOrgSite();
|
||||||
|
testRouteStackOrdering();
|
||||||
|
testSecurityScenarios();
|
||||||
@@ -71,6 +71,15 @@ export async function verifySiteAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.userOrgId && site.orgId !== req.userOrgId) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.FORBIDDEN,
|
||||||
|
"User does not have access to this site"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!req.userOrg) {
|
if (!req.userOrg) {
|
||||||
// Get user's role ID in the organization
|
// Get user's role ID in the organization
|
||||||
const userOrgRole = await db
|
const userOrgRole = await db
|
||||||
@@ -128,10 +137,7 @@ export async function verifySiteAccess(
|
|||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(roleSites.siteId, site.siteId),
|
eq(roleSites.siteId, site.siteId),
|
||||||
inArray(
|
inArray(roleSites.roleId, req.userOrgRoleIds!)
|
||||||
roleSites.roleId,
|
|
||||||
req.userOrgRoleIds!
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
|
|||||||
@@ -493,16 +493,29 @@ export async function getTraefikConfig(
|
|||||||
const transportName = `${key}-transport`;
|
const transportName = `${key}-transport`;
|
||||||
const headersMiddlewareName = `${key}-headers-middleware`;
|
const headersMiddlewareName = `${key}-headers-middleware`;
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`Processing resource ${resource.name} with domain ${fullDomain} and ${targets.length} targets`
|
||||||
|
);
|
||||||
|
|
||||||
if (!resource.enabled) {
|
if (!resource.enabled) {
|
||||||
|
logger.debug(
|
||||||
|
`Resource ${resource.name} is disabled, skipping Traefik config`
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resource.http) {
|
if (resource.mode == "http") {
|
||||||
if (!resource.domainId) {
|
if (!resource.domainId) {
|
||||||
|
logger.debug(
|
||||||
|
`Resource ${resource.name} does not have a domainId, skipping Traefik config`
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resource.fullDomain) {
|
if (!resource.fullDomain) {
|
||||||
|
logger.debug(
|
||||||
|
`Resource ${resource.name} does not have a fullDomain, skipping Traefik config`
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -958,7 +971,7 @@ export async function getTraefikConfig(
|
|||||||
serviceName
|
serviceName
|
||||||
].loadBalancer.serversTransport = transportName;
|
].loadBalancer.serversTransport = transportName;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (resource.mode == "tcp" || resource.mode == "udp") {
|
||||||
// Non-HTTP (TCP/UDP) configuration
|
// Non-HTTP (TCP/UDP) configuration
|
||||||
if (!resource.enableProxy) {
|
if (!resource.enableProxy) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { randomInt } from "crypto";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
actionAuditLog,
|
actionAuditLog,
|
||||||
@@ -392,7 +393,7 @@ export async function signSshKey(
|
|||||||
if (existingUserWithSameName) {
|
if (existingUserWithSameName) {
|
||||||
let foundUniqueUsername = false;
|
let foundUniqueUsername = false;
|
||||||
for (let attempt = 0; attempt < 20; attempt++) {
|
for (let attempt = 0; attempt < 20; attempt++) {
|
||||||
const randomNum = Math.floor(Math.random() * 101); // 0 to 100
|
const randomNum = randomInt(0, 101); // 0 to 100
|
||||||
const candidateUsername = `${usernameToUse}${randomNum}`;
|
const candidateUsername = `${usernameToUse}${randomNum}`;
|
||||||
|
|
||||||
const [existingUser] = await db
|
const [existingUser] = await db
|
||||||
|
|||||||
@@ -561,6 +561,7 @@ authenticated.delete(
|
|||||||
authenticated.put(
|
authenticated.put(
|
||||||
"/resource/:resourceId/target",
|
"/resource/:resourceId/target",
|
||||||
verifyResourceAccess,
|
verifyResourceAccess,
|
||||||
|
verifySiteAccess,
|
||||||
verifyLimits,
|
verifyLimits,
|
||||||
verifyUserHasAction(ActionsEnum.createTarget),
|
verifyUserHasAction(ActionsEnum.createTarget),
|
||||||
logActionAudit(ActionsEnum.createTarget),
|
logActionAudit(ActionsEnum.createTarget),
|
||||||
@@ -612,6 +613,7 @@ authenticated.get(
|
|||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/target/:targetId",
|
"/target/:targetId",
|
||||||
verifyTargetAccess,
|
verifyTargetAccess,
|
||||||
|
verifySiteAccess,
|
||||||
verifyLimits,
|
verifyLimits,
|
||||||
verifyUserHasAction(ActionsEnum.updateTarget),
|
verifyUserHasAction(ActionsEnum.updateTarget),
|
||||||
logActionAudit(ActionsEnum.updateTarget),
|
logActionAudit(ActionsEnum.updateTarget),
|
||||||
@@ -1234,7 +1236,8 @@ export const authRouter = Router();
|
|||||||
unauthenticated.use("/auth", authRouter);
|
unauthenticated.use("/auth", authRouter);
|
||||||
authRouter.use(
|
authRouter.use(
|
||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: config.getRawConfig().rate_limits.auth.window_minutes * 60 * 1000,
|
windowMs:
|
||||||
|
config.getRawConfig().rate_limits.auth.window_minutes * 60 * 1000,
|
||||||
max: config.getRawConfig().rate_limits.auth.max_requests,
|
max: config.getRawConfig().rate_limits.auth.max_requests,
|
||||||
keyGenerator: (req) =>
|
keyGenerator: (req) =>
|
||||||
`authRouterGlobal:${ipKeyGenerator(req.ip || "")}:${req.path}`,
|
`authRouterGlobal:${ipKeyGenerator(req.ip || "")}:${req.path}`,
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export function ProxyResourceTargetsForm({
|
|||||||
// Notify parent of changes (create mode)
|
// Notify parent of changes (create mode)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange?.(targets);
|
onChange?.(targets);
|
||||||
}, [targets]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [targets]);
|
||||||
|
|
||||||
// Poll health status only in edit mode
|
// Poll health status only in edit mode
|
||||||
const { data: polledTargets } = useQuery({
|
const { data: polledTargets } = useQuery({
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export default async function Page(props: {
|
|||||||
targetOrgId = lastOrgCookie;
|
targetOrgId = lastOrgCookie;
|
||||||
} else {
|
} else {
|
||||||
let ownedOrg = orgs.find((org) => org.isOwner);
|
let ownedOrg = orgs.find((org) => org.isOwner);
|
||||||
let primaryOrg = orgs.find((org) => org.isPrimaryOrg);
|
const primaryOrg = orgs.find((org) => org.isPrimaryOrg);
|
||||||
if (!ownedOrg) {
|
if (!ownedOrg) {
|
||||||
if (primaryOrg) {
|
if (primaryOrg) {
|
||||||
ownedOrg = primaryOrg;
|
ownedOrg = primaryOrg;
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ export const metadata: Metadata = {
|
|||||||
export default async function MaintenanceScreen() {
|
export default async function MaintenanceScreen() {
|
||||||
const t = await getTranslations();
|
const t = await getTranslations();
|
||||||
|
|
||||||
let title = t("privateMaintenanceScreenTitle");
|
const title = t("privateMaintenanceScreenTitle");
|
||||||
let message = t("privateMaintenanceScreenMessage");
|
const message = t("privateMaintenanceScreenMessage");
|
||||||
let steps = t("privateMaintenanceScreenSteps");
|
const steps = t("privateMaintenanceScreenSteps");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center p-4">
|
<div className="min-h-screen flex items-center justify-center p-4">
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default async function RdpPage() {
|
|||||||
const hostname = host.split(":")[0];
|
const hostname = host.split(":")[0];
|
||||||
|
|
||||||
let target: GetBrowserTargetResponse | null = null;
|
let target: GetBrowserTargetResponse | null = null;
|
||||||
let error: string | null = null;
|
const error: string | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await priv.get<AxiosResponse<GetBrowserTargetResponse>>(
|
const res = await priv.get<AxiosResponse<GetBrowserTargetResponse>>(
|
||||||
|
|||||||
@@ -180,7 +180,6 @@ export default function SshClient({
|
|||||||
certificate: signedKeyData.certificate
|
certificate: signedKeyData.certificate
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function connect(override?: ConnectCredentials) {
|
function connect(override?: ConnectCredentials) {
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ export default function VncClient({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [connected, setConnected] = useState(false);
|
const [connected, setConnected] = useState(false);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const rfbRef = useRef<any>(null);
|
const rfbRef = useRef<any>(null);
|
||||||
const screenRef = useRef<HTMLDivElement>(null);
|
const screenRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -59,7 +58,7 @@ export default function VncClient({
|
|||||||
// Clean up on unmount.
|
// Clean up on unmount.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => disconnect();
|
return () => disconnect();
|
||||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
}, []);
|
||||||
|
|
||||||
const connect = async () => {
|
const connect = async () => {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
@@ -115,7 +114,6 @@ export default function VncClient({
|
|||||||
options.credentials = { password: form.password };
|
options.credentials = { password: form.password };
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const rfb: any = new RFB(screenRef.current, wsUrl, options);
|
const rfb: any = new RFB(screenRef.current, wsUrl, options);
|
||||||
|
|
||||||
rfb.scaleViewport = true;
|
rfb.scaleViewport = true;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default async function VncPage() {
|
|||||||
const hostname = host.split(":")[0];
|
const hostname = host.split(":")[0];
|
||||||
|
|
||||||
let target: GetBrowserTargetResponse | null = null;
|
let target: GetBrowserTargetResponse | null = null;
|
||||||
let error: string | null = null;
|
const error: string | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await priv.get<AxiosResponse<GetBrowserTargetResponse>>(
|
const res = await priv.get<AxiosResponse<GetBrowserTargetResponse>>(
|
||||||
|
|||||||
Reference in New Issue
Block a user