diff --git a/bruno/Auth/login.bru b/bruno/Auth/login.bru index a254f828..3825a252 100644 --- a/bruno/Auth/login.bru +++ b/bruno/Auth/login.bru @@ -12,7 +12,7 @@ post { body:json { { - "email": "owen@fossorial.io", + "email": "admin@fosrl.io", "password": "Password123!" } } diff --git a/bruno/Users/adminListUsers.bru b/bruno/Users/adminListUsers.bru new file mode 100644 index 00000000..cdc41095 --- /dev/null +++ b/bruno/Users/adminListUsers.bru @@ -0,0 +1,11 @@ +meta { + name: adminListUsers + type: http + seq: 2 +} + +get { + url: http://localhost:3000/api/v1/users + body: none + auth: none +} diff --git a/bruno/Users/adminRemoveUser.bru b/bruno/Users/adminRemoveUser.bru new file mode 100644 index 00000000..9e9f3507 --- /dev/null +++ b/bruno/Users/adminRemoveUser.bru @@ -0,0 +1,11 @@ +meta { + name: adminRemoveUser + type: http + seq: 3 +} + +delete { + url: http://localhost:3000/api/v1/user/ky5r7ivqs8wc7u4 + body: none + auth: none +} diff --git a/install/config/config.yml b/install/config/config.yml index 15d48b8d..043b1421 100644 --- a/install/config/config.yml +++ b/install/config/config.yml @@ -47,7 +47,7 @@ newt: rate_limits: global: window_minutes: 1 - max_requests: 100 + max_requests: 500 {{if .EnableEmail}} email: smtp_host: "{{.EmailSMTPHost}}" diff --git a/install/main.go b/install/main.go index 9064b4f7..47c846cf 100644 --- a/install/main.go +++ b/install/main.go @@ -2,19 +2,19 @@ package main import ( "bufio" + "bytes" "embed" "fmt" "io" "io/fs" "os" - "time" "os/exec" "path/filepath" "runtime" "strings" "syscall" - "bytes" "text/template" + "time" "unicode" "golang.org/x/term" @@ -48,8 +48,8 @@ type Config struct { EmailSMTPPass string EmailNoReply string InstallGerbil bool - TraefikBouncerKey string - DoCrowdsecInstall bool + TraefikBouncerKey string + DoCrowdsecInstall bool } func main() { @@ -84,7 +84,7 @@ func main() { } fmt.Println("\n=== Starting installation ===") - + if isDockerInstalled() { if readBool(reader, "Would you like to install and start the containers?", true) { pullAndStartContainers() @@ -95,33 +95,35 @@ func main() { } if !checkIsCrowdsecInstalledInCompose() { - fmt.Println("\n=== Crowdsec Install ===") + fmt.Println("\n=== CrowdSec Install ===") // check if crowdsec is installed - if readBool(reader, "Would you like to install Crowdsec?", true) { + if readBool(reader, "Would you like to install CrowdSec?", false) { + fmt.Println("This installer constitutes a minimal viable CrowdSec deployment. CrowdSec will add extra complexity to your Pangolin installation and may not work to the best of its abilities out of the box. Users are expected to implement configuration adjustments on their own to achieve the best security posture. Consult the CrowdSec documentation for detailed configuration instructions.") + if readBool(reader, "Are you willing to manage CrowdSec?", false) { + if config.DashboardDomain == "" { + traefikConfig, err := ReadTraefikConfig("config/traefik/traefik_config.yml", "config/traefik/dynamic_config.yml") + if err != nil { + fmt.Printf("Error reading config: %v\n", err) + return + } + config.DashboardDomain = traefikConfig.DashboardDomain + config.LetsEncryptEmail = traefikConfig.LetsEncryptEmail + config.BadgerVersion = traefikConfig.BadgerVersion - if config.DashboardDomain == "" { - traefikConfig, err := ReadTraefikConfig("config/traefik/traefik_config.yml", "config/traefik/dynamic_config.yml") - if err != nil { - fmt.Printf("Error reading config: %v\n", err) - return - } - config.DashboardDomain = traefikConfig.DashboardDomain - config.LetsEncryptEmail = traefikConfig.LetsEncryptEmail - config.BadgerVersion = traefikConfig.BadgerVersion - - // print the values and check if they are right - fmt.Println("Detected values:") - fmt.Printf("Dashboard Domain: %s\n", config.DashboardDomain) - fmt.Printf("Let's Encrypt Email: %s\n", config.LetsEncryptEmail) - fmt.Printf("Badger Version: %s\n", config.BadgerVersion) + // print the values and check if they are right + fmt.Println("Detected values:") + fmt.Printf("Dashboard Domain: %s\n", config.DashboardDomain) + fmt.Printf("Let's Encrypt Email: %s\n", config.LetsEncryptEmail) + fmt.Printf("Badger Version: %s\n", config.BadgerVersion) - if !readBool(reader, "Are these values correct?", true) { - config = collectUserInput(reader) + if !readBool(reader, "Are these values correct?", true) { + config = collectUserInput(reader) + } } + + config.DoCrowdsecInstall = true + installCrowdsec(config) } - - config.DoCrowdsecInstall = true - installCrowdsec(config) } } @@ -143,23 +145,23 @@ func readString(reader *bufio.Reader, prompt string, defaultValue string) string } func readPassword(prompt string, reader *bufio.Reader) string { - if term.IsTerminal(int(syscall.Stdin)) { - fmt.Print(prompt + ": ") - // Read password without echo if we're in a terminal - password, err := term.ReadPassword(int(syscall.Stdin)) - fmt.Println() // Add a newline since ReadPassword doesn't add one - if err != nil { - return "" - } - input := strings.TrimSpace(string(password)) - if input == "" { - return readPassword(prompt, reader) - } - return input - } else { + if term.IsTerminal(int(syscall.Stdin)) { + fmt.Print(prompt + ": ") + // Read password without echo if we're in a terminal + password, err := term.ReadPassword(int(syscall.Stdin)) + fmt.Println() // Add a newline since ReadPassword doesn't add one + if err != nil { + return "" + } + input := strings.TrimSpace(string(password)) + if input == "" { + return readPassword(prompt, reader) + } + return input + } else { // Fallback to reading from stdin if not in a terminal return readString(reader, prompt, "") - } + } } func readBool(reader *bufio.Reader, prompt string, defaultValue bool) bool { @@ -319,15 +321,15 @@ func createConfigFiles(config Config) error { if !config.DoCrowdsecInstall && strings.Contains(path, "crowdsec") { return nil } - + if config.DoCrowdsecInstall && !strings.Contains(path, "crowdsec") { return nil } - // skip .DS_Store - if strings.Contains(path, ".DS_Store") { - return nil - } + // skip .DS_Store + if strings.Contains(path, ".DS_Store") { + return nil + } if d.IsDir() { // Create directory @@ -376,7 +378,6 @@ func createConfigFiles(config Config) error { return nil } - func installDocker() error { // Detect Linux distribution cmd := exec.Command("cat", "/etc/os-release") @@ -654,29 +655,29 @@ func moveFile(src, dst string) error { } func waitForContainer(containerName string) error { - maxAttempts := 30 - retryInterval := time.Second * 2 + maxAttempts := 30 + retryInterval := time.Second * 2 - for attempt := 0; attempt < maxAttempts; attempt++ { - // Check if container is running - cmd := exec.Command("docker", "container", "inspect", "-f", "{{.State.Running}}", containerName) - var out bytes.Buffer - cmd.Stdout = &out - - if err := cmd.Run(); err != nil { - // If the container doesn't exist or there's another error, wait and retry - time.Sleep(retryInterval) - continue - } + for attempt := 0; attempt < maxAttempts; attempt++ { + // Check if container is running + cmd := exec.Command("docker", "container", "inspect", "-f", "{{.State.Running}}", containerName) + var out bytes.Buffer + cmd.Stdout = &out - isRunning := strings.TrimSpace(out.String()) == "true" - if isRunning { - return nil - } + if err := cmd.Run(); err != nil { + // If the container doesn't exist or there's another error, wait and retry + time.Sleep(retryInterval) + continue + } - // Container exists but isn't running yet, wait and retry - time.Sleep(retryInterval) - } + isRunning := strings.TrimSpace(out.String()) == "true" + if isRunning { + return nil + } - return fmt.Errorf("container %s did not start within %v seconds", containerName, maxAttempts*int(retryInterval.Seconds())) -} \ No newline at end of file + // Container exists but isn't running yet, wait and retry + time.Sleep(retryInterval) + } + + return fmt.Errorf("container %s did not start within %v seconds", containerName, maxAttempts*int(retryInterval.Seconds())) +} diff --git a/package-lock.json b/package-lock.json index a1ee78cf..c9e0a334 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,6 @@ "@radix-ui/react-switch": "1.1.2", "@radix-ui/react-tabs": "1.1.2", "@radix-ui/react-toast": "1.2.4", - "@radix-ui/react-tooltip": "1.1.8", "@react-email/components": "0.0.31", "@react-email/tailwind": "1.0.4", "@tanstack/react-table": "8.20.6", @@ -47,6 +46,7 @@ "glob": "11.0.0", "helmet": "8.0.0", "http-errors": "2.0.0", + "i": "^0.3.7", "input-otp": "1.4.1", "js-yaml": "4.1.0", "lucide-react": "0.469.0", @@ -56,12 +56,14 @@ "node-cache": "5.1.2", "node-fetch": "3.3.2", "nodemailer": "6.9.16", + "npm": "^11.2.0", "oslo": "1.2.1", "qrcode.react": "4.2.0", "react": "19.0.0", "react-dom": "19.0.0", "react-easy-sort": "^1.6.0", "react-hook-form": "7.54.2", + "react-icons": "^5.5.0", "rebuild": "0.1.2", "semver": "7.6.3", "tailwind-merge": "2.6.0", @@ -3711,210 +3713,6 @@ } } }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz", - "integrity": "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.5", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.2", - "@radix-ui/react-portal": "1.1.4", - "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-slot": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", - "integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", - "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz", - "integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.2", - "@radix-ui/react-compose-refs": "1.1.1", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-use-rect": "1.1.0", - "@radix-ui/react-use-size": "1.1.0", - "@radix-ui/rect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", - "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.2", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", - "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", - "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz", - "integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -8705,6 +8503,14 @@ "node": ">=10.17.0" } }, + "node_modules/i": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", + "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -10014,6 +9820,162 @@ "node": ">=0.10.0" } }, + "node_modules/npm": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.2.0.tgz", + "integrity": "sha512-PcnFC6gTo9VDkxVaQ1/mZAS3JoWrDjAI+a6e2NgfYQSGDwftJlbdV0jBMi2V8xQPqbGcWaa7p3UP0SKF+Bhm2g==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which" + ], + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^9.0.1", + "@npmcli/config": "^10.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.1.1", + "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/redact": "^3.1.1", + "@npmcli/run-script": "^9.0.1", + "@sigstore/tuf": "^3.0.0", + "abbrev": "^3.0.0", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.4.1", + "ci-info": "^4.1.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.5", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.0.2", + "ini": "^5.0.0", + "init-package-json": "^8.0.0", + "is-cidr": "^5.1.1", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^10.0.0", + "libnpmdiff": "^8.0.1", + "libnpmexec": "^10.1.0", + "libnpmfund": "^7.0.1", + "libnpmorg": "^8.0.0", + "libnpmpack": "^9.0.1", + "libnpmpublish": "^11.0.0", + "libnpmsearch": "^9.0.0", + "libnpmteam": "^8.0.0", + "libnpmversion": "^8.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.1.0", + "nopt": "^8.1.0", + "normalize-package-data": "^7.0.0", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.1", + "npm-package-arg": "^12.0.2", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^7.0.3", + "pacote": "^21.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.1.0", + "semver": "^7.7.1", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^10.0.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.0", + "which": "^5.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -10027,6 +9989,2293 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "9.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/metavuln-calculator": "^9.0.0", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.1", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "bin-links": "^5.0.0", + "cacache": "^19.0.1", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^8.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.1", + "pacote": "^21.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "ssri": "^12.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "10.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.1.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "6.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^21.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "6.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.1.2" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "3.1.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "9.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "2.0.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.4.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "2.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "19.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.1.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.3", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.4.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/diff": { + "version": "7.0.0", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.3.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.4.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^6.1.0", + "npm-package-arg": "^12.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^4.1.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "3.4.3", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "8.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.0.1", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^3.0.0", + "diff": "^7.0.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^21.0.0", + "tar": "^6.2.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "10.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.0.1", + "@npmcli/package-json": "^6.1.1", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^21.0.0", + "proc-log": "^5.0.0", + "read": "^4.0.0", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "7.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "9.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.0.1", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^21.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "11.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^7.0.0", + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1", + "proc-log": "^5.0.0", + "semver": "^7.3.7", + "sigstore": "^3.0.0", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.1", + "@npmcli/run-script": "^9.0.1", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.4.3", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "14.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.1.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "11.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "8.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "7.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "7.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "12.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "11.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "18.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "3.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "7.0.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.1", + "inBundle": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/npm/node_modules/pacote": { + "version": "21.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^10.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^2.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/rimraf": { + "version": "5.0.10", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.7.1", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.21", + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ssri": { + "version": "12.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "10.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/npm/node_modules/which": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -11984,6 +14233,15 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 4bcb2d40..08cb73aa 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "glob": "11.0.0", "helmet": "8.0.0", "http-errors": "2.0.0", + "i": "^0.3.7", "input-otp": "1.4.1", "js-yaml": "4.1.0", "lucide-react": "0.469.0", @@ -66,12 +67,14 @@ "node-cache": "5.1.2", "node-fetch": "3.3.2", "nodemailer": "6.9.16", + "npm": "^11.2.0", "oslo": "1.2.1", "qrcode.react": "4.2.0", "react": "19.0.0", "react-dom": "19.0.0", "react-easy-sort": "^1.6.0", "react-hook-form": "7.54.2", + "react-icons": "^5.5.0", "rebuild": "0.1.2", "semver": "7.6.3", "tailwind-merge": "2.6.0", diff --git a/server/db/schema.ts b/server/db/schema.ts index 0953d29e..74e9ecf2 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -471,6 +471,15 @@ export const resourceRules = sqliteTable("resourceRules", { value: text("value").notNull() }); +export const supporterKey = sqliteTable("supporterKey", { + keyId: integer("keyId").primaryKey({ autoIncrement: true }), + key: text("key").notNull(), + githubUsername: text("githubUsername").notNull(), + phrase: text("phrase"), + tier: text("tier"), + valid: integer("valid", { mode: "boolean" }).notNull().default(false) +}); + export type Org = InferSelectModel; export type User = InferSelectModel; export type Site = InferSelectModel; @@ -510,3 +519,4 @@ export type Client = InferSelectModel; export type RoleClient = InferSelectModel; export type UserClient = InferSelectModel; export type Domain = InferSelectModel; +export type SupporterKey = InferSelectModel; diff --git a/server/lib/config.ts b/server/lib/config.ts index da7d4e7a..b41be6ec 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -10,7 +10,9 @@ import { } from "@server/lib/consts"; import { passwordSchema } from "@server/auth/passwordSchema"; import stoi from "./stoi"; -import { start } from "repl"; +import db from "@server/db"; +import { SupporterKey, supporterKey } from "@server/db/schema"; +import { eq } from "drizzle-orm"; const portSchema = z.number().positive().gt(0).lte(65535); @@ -162,6 +164,10 @@ const configSchema = z.object({ export class Config { private rawConfig!: z.infer; + supporterData: SupporterKey | null = null; + + supporterHiddenUntil: number | null = null; + constructor() { this.loadConfig(); } @@ -190,7 +196,9 @@ export class Config { } if (process.env.APP_BASE_DOMAIN) { - console.log("You're using deprecated environment variables. Transition to the configuration file. https://docs.fossorial.io/"); + console.log( + "You're using deprecated environment variables. Transition to the configuration file. https://docs.fossorial.io/" + ); } if (!environment) { @@ -242,6 +250,14 @@ export class Config { : "false"; process.env.DASHBOARD_URL = parsedConfig.data.app.dashboard_url; + this.checkSupporterKey() + .then(() => { + console.log("Supporter key checked"); + }) + .catch((error) => { + console.error("Error checking supporter key:", error); + }); + this.rawConfig = parsedConfig.data; } @@ -258,6 +274,85 @@ export class Config { public getDomain(domainId: string) { return this.rawConfig.domains[domainId]; } + + public hideSupporterKey(days: number = 7) { + const now = new Date().getTime(); + + if (this.supporterHiddenUntil && now < this.supporterHiddenUntil) { + return; + } + + this.supporterHiddenUntil = now + 1000 * 60 * 60 * 24 * days; + } + + public isSupporterKeyHidden() { + const now = new Date().getTime(); + + if (this.supporterHiddenUntil && now < this.supporterHiddenUntil) { + return true; + } + + return false; + } + + public async checkSupporterKey() { + const [key] = await db.select().from(supporterKey).limit(1); + + if (!key) { + return; + } + + const { key: licenseKey, githubUsername } = key; + + const response = await fetch( + "https://api.dev.fossorial.io/api/v1/license/validate", + { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + licenseKey, + githubUsername + }) + } + ); + + if (!response.ok) { + this.supporterData = key; + return; + } + + const data = await response.json(); + + if (!data.data.valid) { + this.supporterData = { + ...key, + valid: false + }; + return; + } + + this.supporterData = { + ...key, + tier: data.data.tier, + valid: true + }; + + // update the supporter key in the database + await db + .update(supporterKey) + .set({ + tier: data.data.tier || null, + phrase: data.data.cutePhrase || null, + valid: true + }) + .where(eq(supporterKey.keyId, key.keyId)); + } + + public getSupporterData() { + return this.supporterData; + } } export const config = new Config(); diff --git a/server/lib/consts.ts b/server/lib/consts.ts index 2c35427a..87468c2b 100644 --- a/server/lib/consts.ts +++ b/server/lib/consts.ts @@ -2,7 +2,7 @@ import path from "path"; import { fileURLToPath } from "url"; // This is a placeholder value replaced by the build process -export const APP_VERSION = "1.0.0"; +export const APP_VERSION = "1.1.0"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts index a0f01d08..438d9c59 100644 --- a/server/middlewares/index.ts +++ b/server/middlewares/index.ts @@ -15,3 +15,4 @@ export * from "./verifySetResourceUsers"; export * from "./verifyUserInRole"; export * from "./verifyAccessTokenAccess"; export * from "./verifyClientAccess"; +export * from "./verifyUserIsServerAdmin"; diff --git a/server/middlewares/verifyUserIsServerAdmin.ts b/server/middlewares/verifyUserIsServerAdmin.ts new file mode 100644 index 00000000..9088a425 --- /dev/null +++ b/server/middlewares/verifyUserIsServerAdmin.ts @@ -0,0 +1,37 @@ +import { Request, Response, NextFunction } from "express"; +import createHttpError from "http-errors"; +import HttpCode from "@server/types/HttpCode"; + +export async function verifyUserIsServerAdmin( + req: Request, + res: Response, + next: NextFunction +) { + const userId = req.user!.userId; + + if (!userId) { + return next( + createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated") + ); + } + + try { + if (!req.user?.serverAdmin) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User is not a server admin" + ) + ); + } + + return next(); + } catch (e) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Error verifying organization access" + ) + ); + } +} diff --git a/server/routers/external.ts b/server/routers/external.ts index 7a0d466a..6b01aacc 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -9,6 +9,7 @@ import * as user from "./user"; import * as auth from "./auth"; import * as role from "./role"; import * as client from "./client"; +import * as supporterKey from "./supporterKey"; import * as accessToken from "./accessToken"; import HttpCode from "@server/types/HttpCode"; import { @@ -24,7 +25,8 @@ import { verifySetResourceUsers, verifyUserAccess, getUserOrgs, - verifyClientAccess + verifyClientAccess, + verifyUserIsServerAdmin } from "@server/middlewares"; import { verifyUserHasAction } from "../middlewares/verifyUserHasAction"; import { ActionsEnum } from "@server/auth/actions"; @@ -413,6 +415,9 @@ authenticated.get( authenticated.get(`/org/:orgId/overview`, verifyOrgAccess, org.getOrgOverview); +authenticated.post(`/supporter-key/validate`, supporterKey.validateSupporterKey); +authenticated.post(`/supporter-key/hide`, supporterKey.hideSupporterKey); + unauthenticated.get("/resource/:resourceId/auth", resource.getResourceAuthInfo); // authenticated.get( @@ -446,6 +451,13 @@ unauthenticated.get("/resource/:resourceId/auth", resource.getResourceAuthInfo); unauthenticated.get("/user", verifySessionMiddleware, user.getUser); +authenticated.get("/users", verifyUserIsServerAdmin, user.adminListUsers); +authenticated.delete( + "/user/:userId", + verifyUserIsServerAdmin, + user.adminRemoveUser +); + authenticated.get("/org/:orgId/user/:userId", verifyOrgAccess, user.getOrgUser); authenticated.get( "/org/:orgId/users", diff --git a/server/routers/internal.ts b/server/routers/internal.ts index 6b57e3f6..05dcb0d1 100644 --- a/server/routers/internal.ts +++ b/server/routers/internal.ts @@ -4,8 +4,12 @@ import * as traefik from "@server/routers/traefik"; import * as resource from "./resource"; import * as badger from "./badger"; import * as auth from "@server/routers/auth"; +import * as supporterKey from "@server/routers/supporterKey"; import HttpCode from "@server/types/HttpCode"; -import { verifyResourceAccess, verifySessionUserMiddleware } from "@server/middlewares"; +import { + verifyResourceAccess, + verifySessionUserMiddleware +} from "@server/middlewares"; // Root routes const internalRouter = Router(); @@ -28,6 +32,11 @@ internalRouter.post( resource.getExchangeToken ); +internalRouter.get( + `/supporter-key/visible`, + supporterKey.isSupporterKeyVisible +); + // Gerbil routes const gerbilRouter = Router(); internalRouter.use("/gerbil", gerbilRouter); diff --git a/server/routers/supporterKey/hideSupporterKey.ts b/server/routers/supporterKey/hideSupporterKey.ts new file mode 100644 index 00000000..f9d4e89b --- /dev/null +++ b/server/routers/supporterKey/hideSupporterKey.ts @@ -0,0 +1,35 @@ +import { Request, Response, NextFunction } from "express"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { response as sendResponse } from "@server/lib"; +import config from "@server/lib/config"; + +export type HideSupporterKeyResponse = { + hidden: boolean; +}; + +export async function hideSupporterKey( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + config.hideSupporterKey(); + + return sendResponse(res, { + data: { + hidden: true + }, + success: true, + error: false, + message: "Hidden", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/supporterKey/index.ts b/server/routers/supporterKey/index.ts new file mode 100644 index 00000000..4e339a69 --- /dev/null +++ b/server/routers/supporterKey/index.ts @@ -0,0 +1,3 @@ +export * from "./validateSupporterKey"; +export * from "./isSupporterKeyVisible"; +export * from "./hideSupporterKey"; diff --git a/server/routers/supporterKey/isSupporterKeyVisible.ts b/server/routers/supporterKey/isSupporterKeyVisible.ts new file mode 100644 index 00000000..0247aca6 --- /dev/null +++ b/server/routers/supporterKey/isSupporterKeyVisible.ts @@ -0,0 +1,54 @@ +import { Request, Response, NextFunction } from "express"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { response as sendResponse } from "@server/lib"; +import config from "@server/lib/config"; +import db from "@server/db"; +import { count } from "drizzle-orm"; +import { users } from "@server/db/schema"; + +export type IsSupporterKeyVisibleResponse = { + visible: boolean; +}; + +const USER_LIMIT = 5; + +export async function isSupporterKeyVisible( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const hidden = config.isSupporterKeyHidden(); + const key = config.getSupporterData(); + + let visible = !hidden && key?.valid !== true; + + if (key?.tier === "Limited Supporter") { + const [numUsers] = await db.select({ count: count() }).from(users); + + if (numUsers.count > USER_LIMIT) { + visible = true; + } + } + + logger.debug(`Supporter key visible: ${visible}`); + logger.debug(JSON.stringify(key)); + + return sendResponse(res, { + data: { + visible + }, + success: true, + error: false, + message: "Status", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/supporterKey/validateSupporterKey.ts b/server/routers/supporterKey/validateSupporterKey.ts new file mode 100644 index 00000000..bbe7ed48 --- /dev/null +++ b/server/routers/supporterKey/validateSupporterKey.ts @@ -0,0 +1,115 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { response as sendResponse } from "@server/lib"; +import { suppressDeprecationWarnings } from "moment"; +import { supporterKey } from "@server/db/schema"; +import db from "@server/db"; +import { eq } from "drizzle-orm"; +import config from "@server/lib/config"; + +const validateSupporterKeySchema = z + .object({ + githubUsername: z.string().nonempty(), + key: z.string().nonempty() + }) + .strict(); + +export type ValidateSupporterKeyResponse = { + valid: boolean; + githubUsername?: string; + tier?: string; + phrase?: string; +}; + +export async function validateSupporterKey( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedBody = validateSupporterKeySchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const { githubUsername, key } = parsedBody.data; + + const response = await fetch( + "https://api.dev.fossorial.io/api/v1/license/validate", + { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + licenseKey: key, + githubUsername: githubUsername + }) + } + ); + + if (!response.ok) { + logger.error(response); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "An error occurred" + ) + ); + } + + const data = await response.json(); + + if (!data || !data.data.valid) { + return sendResponse(res, { + data: { + valid: false + }, + success: true, + error: false, + message: "Invalid supporter key", + status: HttpCode.OK + }); + } + + await db.transaction(async (trx) => { + await trx.delete(supporterKey); + await trx.insert(supporterKey).values({ + githubUsername: githubUsername, + key: key, + tier: data.data.tier || null, + phrase: data.data.cutePhrase || null, + valid: true + }); + }); + + await config.checkSupporterKey(); + + return sendResponse(res, { + data: { + valid: true, + githubUsername: data.data.githubUsername, + tier: data.data.tier, + phrase: data.data.cutePhrase + }, + success: true, + error: false, + message: "Valid supporter key", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index c6488412..8506a2a1 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -170,6 +170,10 @@ export async function traefikConfigProvider( wildCard = `*.${domainParts.slice(1).join(".")}`; } + if (resource.isBaseDomain) { + wildCard = resource.fullDomain; + } + const configDomain = config.getDomain(resource.domainId); if (!configDomain) { diff --git a/server/routers/user/adminListUsers.ts b/server/routers/user/adminListUsers.ts new file mode 100644 index 00000000..e4d5c206 --- /dev/null +++ b/server/routers/user/adminListUsers.ts @@ -0,0 +1,92 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import { sql, eq } from "drizzle-orm"; +import logger from "@server/logger"; +import { users } from "@server/db/schema"; + +const listUsersSchema = z + .object({ + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.number().int().nonnegative()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.number().int().nonnegative()) + }) + .strict(); + +async function queryUsers(limit: number, offset: number) { + return await db + .select({ + id: users.userId, + email: users.email, + dateCreated: users.dateCreated, + }) + .from(users) + .where(eq(users.serverAdmin, false)) + .limit(limit) + .offset(offset); +} + +export type AdminListUsersResponse = { + users: NonNullable>>; + pagination: { total: number; limit: number; offset: number }; +}; + +export async function adminListUsers( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedQuery = listUsersSchema.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + parsedQuery.error.errors.map((e) => e.message).join(", ") + ) + ); + } + const { limit, offset } = parsedQuery.data; + + const allUsers = await queryUsers( + limit, + offset + ); + + const [{ count }] = await db + .select({ count: sql`count(*)` }) + .from(users); + + return response(res, { + data: { + users: allUsers, + pagination: { + total: count, + limit, + offset + } + }, + success: true, + error: false, + message: "Users retrieved successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/user/adminRemoveUser.ts b/server/routers/user/adminRemoveUser.ts new file mode 100644 index 00000000..a8128900 --- /dev/null +++ b/server/routers/user/adminRemoveUser.ts @@ -0,0 +1,61 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { userOrgs, users } from "@server/db/schema"; +import { and, 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"; + +const removeUserSchema = z + .object({ + userId: z.string() + }) + .strict(); + +export async function adminRemoveUser( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = removeUserSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { userId } = parsedParams.data; + + // get the user first + const user = await db + .select() + .from(userOrgs) + .where(eq(userOrgs.userId, userId)); + + if (!user || user.length === 0) { + return next(createHttpError(HttpCode.NOT_FOUND, "User not found")); + } + + await db.delete(users).where(eq(users.userId, userId)); + + return response(res, { + data: null, + success: true, + error: false, + message: "User removed successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/user/index.ts b/server/routers/user/index.ts index 1bd7faa4..22e06c58 100644 --- a/server/routers/user/index.ts +++ b/server/routers/user/index.ts @@ -4,4 +4,6 @@ export * from "./listUsers"; export * from "./addUserRole"; export * from "./inviteUser"; export * from "./acceptInvite"; -export * from "./getOrgUser"; \ No newline at end of file +export * from "./getOrgUser"; +export * from "./adminListUsers"; +export * from "./adminRemoveUser"; \ No newline at end of file diff --git a/server/routers/user/removeUserOrg.ts b/server/routers/user/removeUserOrg.ts index a608b895..d7fe2bd2 100644 --- a/server/routers/user/removeUserOrg.ts +++ b/server/routers/user/removeUserOrg.ts @@ -71,7 +71,7 @@ export async function removeUserOrg( data: null, success: true, error: false, - message: "User remove from org successfully", + message: "User removed from org successfully", status: HttpCode.OK }); } catch (error) { diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts index 87eec21f..ad081b6e 100644 --- a/server/setup/migrations.ts +++ b/server/setup/migrations.ts @@ -17,6 +17,7 @@ import m8 from "./scripts/1.0.0-beta12"; import m13 from "./scripts/1.0.0-beta13"; import m15 from "./scripts/1.0.0-beta15"; import m16 from "./scripts/1.0.0"; +import m17 from "./scripts/1.1.0"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -33,7 +34,8 @@ const migrations = [ { version: "1.0.0-beta.12", run: m8 }, { version: "1.0.0-beta.13", run: m13 }, { version: "1.0.0-beta.15", run: m15 }, - { version: "1.0.0", run: m16 } + { version: "1.0.0", run: m16 }, + { version: "1.1.0", run: m17 } // Add new migrations here as they are created ] as const; diff --git a/server/setup/scripts/1.1.0.ts b/server/setup/scripts/1.1.0.ts new file mode 100644 index 00000000..8bd2cd19 --- /dev/null +++ b/server/setup/scripts/1.1.0.ts @@ -0,0 +1,28 @@ +import db from "@server/db"; +import { sql } from "drizzle-orm"; + +const version = "1.1.0"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + db.transaction((trx) => { + trx.run(sql`CREATE TABLE 'supporterKey' ( + 'keyId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'key' text NOT NULL, + 'githubUsername' text NOT NULL, + 'phrase' text, + 'tier' text, + 'valid' integer DEFAULT false NOT NULL +);`); + }); + + console.log(`Migrated database schema`); + } catch (e) { + console.log("Unable to migrate database schema"); + throw e; + } + + console.log(`${version} migration complete`); +} diff --git a/src/app/[orgId]/settings/layout.tsx b/src/app/[orgId]/settings/layout.tsx index d94da3fb..87eec167 100644 --- a/src/app/[orgId]/settings/layout.tsx +++ b/src/app/[orgId]/settings/layout.tsx @@ -107,23 +107,19 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { return ( <> -
-
-
-
- -
- -
- +
+
+
+ +
+
+
-
- {children} -
+ {children}
); diff --git a/src/app/[orgId]/settings/resources/CreateResourceForm.tsx b/src/app/[orgId]/settings/resources/CreateResourceForm.tsx index 9bb3ecde..227bdf14 100644 --- a/src/app/[orgId]/settings/resources/CreateResourceForm.tsx +++ b/src/app/[orgId]/settings/resources/CreateResourceForm.tsx @@ -764,7 +764,7 @@ export default function CreateResourceForm({ diff --git a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx index 7632a007..e85d6f3b 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx @@ -552,7 +552,7 @@ export default function ResourceRules(props: { path.

- + Actions
    @@ -568,7 +568,6 @@ export default function ResourceRules(props: {
- Matching Criteria diff --git a/src/app/[orgId]/settings/sites/SitesTable.tsx b/src/app/[orgId]/settings/sites/SitesTable.tsx index 9b56aaeb..43ae82a1 100644 --- a/src/app/[orgId]/settings/sites/SitesTable.tsx +++ b/src/app/[orgId]/settings/sites/SitesTable.tsx @@ -331,7 +331,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { columns={columns} data={rows} addSite={() => { - setIsCreateModalOpen(true); + router.push(`/${orgId}/settings/sites/create`); }} /> diff --git a/src/app/[orgId]/settings/sites/create/page.tsx b/src/app/[orgId]/settings/sites/create/page.tsx new file mode 100644 index 00000000..a8002705 --- /dev/null +++ b/src/app/[orgId]/settings/sites/create/page.tsx @@ -0,0 +1,861 @@ +"use client"; + +import { + SettingsContainer, + SettingsSection, + SettingsSectionBody, + SettingsSectionDescription, + SettingsSectionForm, + SettingsSectionHeader, + SettingsSectionTitle +} from "@app/components/Settings"; +import { StrategySelect } from "@app/components/StrategySelect"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage +} from "@app/components/ui/form"; +import HeaderTitle from "@app/components/SettingsSectionTitle"; +import { z } from "zod"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Input } from "@app/components/ui/input"; +import { Terminal, InfoIcon } from "lucide-react"; +import { Button } from "@app/components/ui/button"; +import CopyTextBox from "@app/components/CopyTextBox"; +import CopyToClipboard from "@app/components/CopyToClipboard"; +import { + InfoSection, + InfoSectionContent, + InfoSections, + InfoSectionTitle +} from "@app/components/InfoSection"; +import { FaWindows, FaApple, FaFreebsd, FaDocker } from "react-icons/fa"; +import { Checkbox } from "@app/components/ui/checkbox"; +import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; +import { generateKeypair } from "../[niceId]/wireguardConfig"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { + CreateSiteBody, + CreateSiteResponse, + PickSiteDefaultsResponse +} from "@server/routers/site"; +import { toast } from "@app/hooks/useToast"; +import { AxiosResponse } from "axios"; +import { useParams, useRouter } from "next/navigation"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator +} from "@app/components/ui/breadcrumb"; +import Link from "next/link"; + +const createSiteFormSchema = z + .object({ + name: z + .string() + .min(2, { + message: "Name must be at least 2 characters." + }) + .max(30, { + message: "Name must not be longer than 30 characters." + }), + method: z.string(), + copied: z.boolean() + }) + .refine( + (data) => { + if (data.method !== "local") { + return data.copied; + } + return true; + }, + { + message: "Please confirm that you have copied the config.", + path: ["copied"] + } + ); + +type CreateSiteFormValues = z.infer; + +type Commands = { + mac: Record; + linux: Record; + windows: Record; + docker: Record; +}; + +export default function Page() { + const { env } = useEnvContext(); + const api = createApiClient({ env }); + const { orgId } = useParams(); + const router = useRouter(); + + const [tunnelTypes, setTunnelTypes] = useState([ + { + id: "newt", + title: "Newt Tunnel (Recommended)", + description: + "Easiest way to create an entrypoint into your network. No extra setup.", + disabled: true + }, + { + id: "wireguard", + title: "Basic WireGuard", + description: + "Use any WireGuard client to establish a tunnel. Manual NAT setup required.", + disabled: true + }, + { + id: "local", + title: "Local", + description: "Local resources only. No tunneling." + } + ]); + + const [loadingPage, setLoadingPage] = useState(true); + + const [platform, setPlatform] = useState("linux"); + const [architecture, setArchitecture] = useState("amd64"); + const [commands, setCommands] = useState(null); + + const [newtId, setNewtId] = useState(""); + const [newtSecret, setNewtSecret] = useState(""); + const [newtEndpoint, setNewtEndpoint] = useState(""); + + const [publicKey, setPublicKey] = useState(""); + const [privateKey, setPrivateKey] = useState(""); + const [wgConfig, setWgConfig] = useState(""); + + const [createLoading, setCreateLoading] = useState(false); + + const [siteDefaults, setSiteDefaults] = + useState(null); + + const hydrateWireGuardConfig = ( + privateKey: string, + publicKey: string, + subnet: string, + address: string, + endpoint: string, + listenPort: string + ) => { + const wgConfig = `[Interface] +Address = ${subnet} +ListenPort = 51820 +PrivateKey = ${privateKey} + +[Peer] +PublicKey = ${publicKey} +AllowedIPs = ${address.split("/")[0]}/32 +Endpoint = ${endpoint}:${listenPort} +PersistentKeepalive = 5`; + setWgConfig(wgConfig); + }; + + const hydrateCommands = ( + id: string, + secret: string, + endpoint: string, + version: string + ) => { + const commands = { + mac: { + "Apple Silicon (arm64)": [ + `curl -L -o newt "https://github.com/fosrl/newt/releases/download/${version}/newt_darwin_arm64" && chmod +x ./newt`, + `./newt --id ${id} --secret ${secret} --endpoint ${endpoint}` + ], + "Intel x64 (amd64)": [ + `curl -L -o newt "https://github.com/fosrl/newt/releases/download/${version}/newt_darwin_amd64" && chmod +x ./newt`, + `./newt --id ${id} --secret ${secret} --endpoint ${endpoint}` + ] + }, + linux: { + amd64: [ + `wget -O newt "https://github.com/fosrl/newt/releases/download/${version}/newt_linux_amd64" && chmod +x ./newt`, + `./newt --id ${id} --secret ${secret} --endpoint ${endpoint}` + ], + arm64: [ + `wget -O newt "https://github.com/fosrl/newt/releases/download/${version}/newt_linux_arm64" && chmod +x ./newt`, + `./newt --id ${id} --secret ${secret} --endpoint ${endpoint}` + ], + arm32: [ + `wget -O newt "https://github.com/fosrl/newt/releases/download/${version}/newt_linux_arm32" && chmod +x ./newt`, + `./newt --id ${id} --secret ${secret} --endpoint ${endpoint}` + ], + arm32v6: [ + `wget -O newt "https://github.com/fosrl/newt/releases/download/${version}/newt_linux_arm32v6" && chmod +x ./newt`, + `./newt --id ${id} --secret ${secret} --endpoint ${endpoint}` + ], + riscv64: [ + `wget -O newt "https://github.com/fosrl/newt/releases/download/${version}/newt_linux_riscv64" && chmod +x ./newt`, + `./newt --id ${id} --secret ${secret} --endpoint ${endpoint}` + ] + }, + freebsd: { + amd64: [ + `fetch -o newt "https://github.com/fosrl/newt/releases/download/${version}/newt_freebsd_amd64" && chmod +x ./newt`, + `./newt --id ${id} --secret ${secret} --endpoint ${endpoint}` + ], + arm64: [ + `fetch -o newt "https://github.com/fosrl/newt/releases/download/${version}/newt_freebsd_arm64" && chmod +x ./newt`, + `./newt --id ${id} --secret ${secret} --endpoint ${endpoint}` + ] + }, + windows: { + x64: [ + `curl -o newt.exe -L "https://github.com/fosrl/newt/releases/download/${version}/newt_windows_amd64.exe"`, + `newt.exe --id ${id} --secret ${secret} --endpoint ${endpoint}` + ] + }, + docker: { + "Docker Compose": [ + `services: + newt: + image: fosrl/newt + container_name: newt + restart: unless-stopped + environment: + - PANGOLIN_ENDPOINT=${endpoint} + - NEWT_ID=${id} + - NEWT_SECRET=${secret}` + ], + "Docker Run": [ + `docker run -it fosrl/newt --id ${id} --secret ${secret} --endpoint ${endpoint}` + ] + } + }; + setCommands(commands); + }; + + const getArchitectures = () => { + switch (platform) { + case "linux": + return ["amd64", "arm64", "arm32", "arm32v6", "riscv64"]; + case "mac": + return ["Apple Silicon (arm64)", "Intel x64 (amd64)"]; + case "windows": + return ["x64"]; + case "docker": + return ["Docker Compose", "Docker Run"]; + case "freebsd": + return ["amd64", "arm64"]; + default: + return ["x64"]; + } + }; + + const getPlatformName = (platformName: string) => { + switch (platformName) { + case "windows": + return "Windows"; + case "mac": + return "macOS"; + case "docker": + return "Docker"; + case "freebsd": + return "FreeBSD"; + default: + return "Linux"; + } + }; + + const getCommand = () => { + const placeholder = ["Unknown command"]; + if (!commands) { + return placeholder; + } + let platformCommands = commands[platform as keyof Commands]; + + if (!platformCommands) { + // get first key + const firstPlatform = Object.keys(commands)[0]; + platformCommands = commands[firstPlatform as keyof Commands]; + + setPlatform(firstPlatform); + } + + let architectureCommands = platformCommands[architecture]; + if (!architectureCommands) { + // get first key + const firstArchitecture = Object.keys(platformCommands)[0]; + architectureCommands = platformCommands[firstArchitecture]; + + setArchitecture(firstArchitecture); + } + + return architectureCommands || placeholder; + }; + + const getPlatformIcon = (platformName: string) => { + switch (platformName) { + case "windows": + return ; + case "mac": + return ; + case "docker": + return ; + case "freebsd": + return ; + default: + return ; + } + }; + + const form = useForm({ + resolver: zodResolver(createSiteFormSchema), + defaultValues: { + name: "", + copied: false, + method: "newt" + } + }); + + async function onSubmit(data: CreateSiteFormValues) { + setCreateLoading(true); + + let payload: CreateSiteBody = { + name: data.name, + type: data.method + }; + + if (data.method == "wireguard") { + if (!siteDefaults || !wgConfig) { + toast({ + variant: "destructive", + title: "Error creating site", + description: "Key pair or site defaults not found" + }); + setCreateLoading(false); + return; + } + + payload = { + ...payload, + subnet: siteDefaults.subnet, + exitNodeId: siteDefaults.exitNodeId, + pubKey: publicKey + }; + } + if (data.method === "newt") { + if (!siteDefaults) { + toast({ + variant: "destructive", + title: "Error creating site", + description: "Site defaults not found" + }); + setCreateLoading(false); + return; + } + + payload = { + ...payload, + subnet: siteDefaults.subnet, + exitNodeId: siteDefaults.exitNodeId, + secret: siteDefaults.newtSecret, + newtId: siteDefaults.newtId + }; + } + + const res = await api + .put< + AxiosResponse + >(`/org/${orgId}/site/`, payload) + .catch((e) => { + toast({ + variant: "destructive", + title: "Error creating site", + description: formatAxiosError(e) + }); + }); + + if (res && res.status === 201) { + const data = res.data.data; + + router.push(`/${orgId}/settings/sites/${data.niceId}`); + } + + setCreateLoading(false); + } + + useEffect(() => { + const load = async () => { + setLoadingPage(true); + + let newtVersion = "latest"; + + try { + const response = await fetch( + `https://api.github.com/repos/fosrl/newt/releases/latest` + ); + if (!response.ok) { + throw new Error( + `Failed to fetch release info: ${response.statusText}` + ); + } + const data = await response.json(); + const latestVersion = data.tag_name; + newtVersion = latestVersion; + } catch (error) { + console.error("Error fetching latest release:", error); + } + + const generatedKeypair = generateKeypair(); + + const privateKey = generatedKeypair.privateKey; + const publicKey = generatedKeypair.publicKey; + + setPrivateKey(privateKey); + setPublicKey(publicKey); + + await api + .get(`/org/${orgId}/pick-site-defaults`) + .catch((e) => { + // update the default value of the form to be local method + form.setValue("method", "local"); + }) + .then((res) => { + if (res && res.status === 200) { + const data = res.data.data; + + setSiteDefaults(data); + + const newtId = data.newtId; + const newtSecret = data.newtSecret; + const newtEndpoint = data.endpoint; + + setNewtId(newtId); + setNewtSecret(newtSecret); + setNewtEndpoint(newtEndpoint); + + hydrateCommands( + newtId, + newtSecret, + env.app.dashboardUrl, + newtVersion + ); + + hydrateWireGuardConfig( + privateKey, + data.publicKey, + data.subnet, + data.address, + data.endpoint, + data.listenPort + ); + + setTunnelTypes((prev: any) => { + return prev.map((item: any) => { + return { + ...item, + disabled: false + }; + }); + }); + } + }); + + setLoadingPage(false); + }; + + load(); + }, []); + + return ( + <> +
+ + + + Sites + + + + Create Site + + + +
+ +
+ + +
+ + {!loadingPage && ( +
+ + + + + Site Information + + + + +
+ + ( + + + Name + + + + + + + This is the the + display name for the + site. + + + )} + /> + + +
+
+
+ + + + + Tunnel Type + + + Determine how you want to connect to your + site + + + + + form.setValue("method", value) + } + cols={3} + /> + + + + {form.watch("method") === "newt" && ( + <> + + + + Newt Credentials + + + This is how Newt will authenticate + with the server + + + + + + + Newt Endpoint + + + + + + + + Newt ID + + + + + + + + Newt Secret Key + + + + + + + + + + + Save Your Credentials + + + You will only be able to see + this once. Make sure to copy it + to a secure place. + + + +
+ + ( + +
+ { + form.setValue( + "copied", + e as boolean + ); + }} + /> + +
+ +
+ )} + /> + + +
+
+ + + + + Install Newt + + + Get Newt running on your system + + + +
+

+ Operating System +

+
+ {[ + "linux", + "docker", + "mac", + "windows", + "freebsd" + ].map((os) => ( + + ))} +
+
+ +
+

+ {platform === "docker" + ? "Method" + : "Architecture"} +

+
+ {getArchitectures().map( + (arch) => ( + + ) + )} +
+
+

+ Commands +

+
+ +
+
+
+
+
+ + )} + + {form.watch("method") === "wireguard" && ( + + + + WireGuard Configuration + + + Use the following configuration to + connect to your network + + + + + + + + + Save Your Credentials + + + You will only be able to see this + once. Make sure to copy it to a + secure place. + + + +
+ + ( + +
+ { + form.setValue( + "copied", + e as boolean + ); + }} + /> + +
+ +
+ )} + /> + + +
+
+ )} +
+ +
+ + +
+
+ )} + + ); +} diff --git a/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx b/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx index 2c0c54e2..2480cd67 100644 --- a/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx +++ b/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx @@ -23,14 +23,7 @@ import { FormLabel, FormMessage } from "@/components/ui/form"; -import { - LockIcon, - Binary, - Key, - User, - Send, - AtSign -} from "lucide-react"; +import { LockIcon, Binary, Key, User, Send, AtSign } from "lucide-react"; import { InputOTP, InputOTPGroup, @@ -50,6 +43,7 @@ import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import Link from "next/link"; +import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext"; const pinSchema = z.object({ pin: z @@ -115,6 +109,8 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { const api = createApiClient({ env }); + const { supporterStatus } = useSupporterStatusContext(); + function getDefaultSelectedMethod() { if (props.methods.sso) { return "sso"; @@ -194,7 +190,10 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { const session = res.data.data.session; if (session) { - window.location.href = appendRequestToken(props.redirect, session); + window.location.href = appendRequestToken( + props.redirect, + session + ); } }) .catch((e) => { @@ -216,7 +215,10 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { setPincodeError(null); const session = res.data.data.session; if (session) { - window.location.href = appendRequestToken(props.redirect, session); + window.location.href = appendRequestToken( + props.redirect, + session + ); } }) .catch((e) => { @@ -241,7 +243,10 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { setPasswordError(null); const session = res.data.data.session; if (session) { - window.location.href = appendRequestToken(props.redirect, session); + window.location.href = appendRequestToken( + props.redirect, + session + ); } }) .catch((e) => { @@ -621,6 +626,15 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { + {supporterStatus?.visible && ( +
+ + Server is running without a supporter key. +
+ Consider supporting the project! +
+
+ )}
) : ( diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a892ccef..225588c1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,14 +6,20 @@ import { ThemeProvider } from "@app/providers/ThemeProvider"; import EnvProvider from "@app/providers/EnvProvider"; import { Separator } from "@app/components/ui/separator"; import { pullEnv } from "@app/lib/pullEnv"; -import { BookOpenText } from "lucide-react"; +import { BookOpenText, ExternalLink } from "lucide-react"; import Image from "next/image"; +import SupportStatusProvider from "@app/providers/SupporterStatusProvider"; +import { createApiClient, internal, priv } from "@app/lib/api"; +import { AxiosResponse } from "axios"; +import { IsSupporterKeyVisibleResponse } from "@server/routers/supporterKey"; export const metadata: Metadata = { title: `Dashboard - Pangolin`, description: "" }; +export const dynamic = 'force-dynamic'; + // const font = Figtree({ subsets: ["latin"] }); const font = Inter({ subsets: ["latin"] }); @@ -24,6 +30,15 @@ export default async function RootLayout({ }>) { const env = pullEnv(); + let supporterData = { + visible: true + }; + + const res = await priv.get< + AxiosResponse + >("supporter-key/visible"); + supporterData.visible = res.data.data.visible; + const version = env.app.version; return ( @@ -36,58 +51,69 @@ export default async function RootLayout({ disableTransitionOnChange > - {/* Main content */} -
{children}
- - {/* Footer */} - + + {/* Footer */} + +
diff --git a/src/app/setup/page.tsx b/src/app/setup/page.tsx index 0cb98a5f..1e416d26 100644 --- a/src/app/setup/page.tsx +++ b/src/app/setup/page.tsx @@ -96,7 +96,8 @@ export default function StepperForm() { }); if (res && res.status === 201) { - setCurrentStep("site"); + // setCurrentStep("site"); + router.push(`/${values.orgId}/settings/sites/create`); } } catch (e) { console.error(e); @@ -290,42 +291,6 @@ export default function StepperForm() { )} - - {currentStep === "site" && ( -
- setLoading(val)} - setChecked={(val) => setIsChecked(val)} - orgId={orgForm.getValues().orgId} - onCreate={() => { - router.push( - `/${orgForm.getValues().orgId}/settings/resources` - ); - }} - /> -
- - -
-
- )} diff --git a/src/components/CopyTextBox.tsx b/src/components/CopyTextBox.tsx index 5743d0e9..877b5a0d 100644 --- a/src/components/CopyTextBox.tsx +++ b/src/components/CopyTextBox.tsx @@ -4,7 +4,11 @@ import { useState, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Copy, Check } from "lucide-react"; -export default function CopyTextBox({ text = "", wrapText = false }) { +export default function CopyTextBox({ + text = "", + wrapText = false, + outline = true +}) { const [isCopied, setIsCopied] = useState(false); const textRef = useRef(null); @@ -23,7 +27,9 @@ export default function CopyTextBox({ text = "", wrapText = false }) { }; return ( -
+
 {
     };
 
     return (
-        
+
{isLink ? ( {text} ) : ( - {text} + + {text} + )} + + + + + + + Limited Supporter + + +

$25

+
    +
  • + + + For 5 or less users + +
  • +
  • + + + Lifetime purchase + +
  • +
  • + + + Supporter status + +
  • +
+
+ + + + + +
+
+ +
+ + +
+ + + + + + + + + + { + setKeyOpen(val); + }} + > + + + Enter Supporter Key + + Meet your very own pet Pangolin! + + + +
+ + ( + + + GitHub Username + + + + + + + )} + /> + ( + + Supporter Key + + + + + + )} + /> + + +
+ + + + + + +
+
+ + {supporterStatus?.visible ? ( + + ) : null} + + ); +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 35593035..a983fdbf 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -21,20 +21,26 @@ const buttonVariants = cva( secondary: "bg-secondary border border-input border-2 text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", + squareOutlinePrimary: + "border-2 border-primary bg-card hover:bg-primary/10 text-primary rounded-md", + squareOutline: + "border-2 border-input bg-card hover:bg-accent hover:text-accent-foreground rounded-md", + squareDefault: + "bg-primary text-primary-foreground hover:bg-primary/90 rounded-md", text: "", - link: "text-primary underline-offset-4 hover:underline", + link: "text-primary underline-offset-4 hover:underline" }, size: { default: "h-9 px-4 py-2", sm: "h-8 rounded-md px-3", lg: "h-10 rounded-md px-8", - icon: "h-9 w-9", + icon: "h-9 w-9" } }, defaultVariants: { variant: "default", - size: "default", - }, + size: "default" + } } ); diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index e3695879..7bfec308 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { cn } from "@app/lib/cn" export function TableContainer({ children }: { children: React.ReactNode }) { - return
{children}
+ return
{children}
} const Table = React.forwardRef< diff --git a/src/contexts/supporterStatusContext.ts b/src/contexts/supporterStatusContext.ts new file mode 100644 index 00000000..9ce88d6b --- /dev/null +++ b/src/contexts/supporterStatusContext.ts @@ -0,0 +1,16 @@ +import { createContext } from "react"; + +export type SupporterStatus = { + visible: boolean; +}; + +type SupporterStatusContextType = { + supporterStatus: SupporterStatus | null; + updateSupporterStatus: (updatedSite: Partial) => void; +}; + +const SupporterStatusContext = createContext< + SupporterStatusContextType | undefined +>(undefined); + +export default SupporterStatusContext; diff --git a/src/hooks/useSupporterStatusContext.ts b/src/hooks/useSupporterStatusContext.ts new file mode 100644 index 00000000..359b4010 --- /dev/null +++ b/src/hooks/useSupporterStatusContext.ts @@ -0,0 +1,12 @@ +import SupporterStatusContext from "@app/contexts/supporterStatusContext"; +import { useContext } from "react"; + +export function useSupporterStatusContext() { + const context = useContext(SupporterStatusContext); + if (context === undefined) { + throw new Error( + "useSupporterStatusContext must be used within an SupporterStatusProvider" + ); + } + return context; +} diff --git a/src/providers/SupporterStatusProvider.tsx b/src/providers/SupporterStatusProvider.tsx new file mode 100644 index 00000000..bcb8be2b --- /dev/null +++ b/src/providers/SupporterStatusProvider.tsx @@ -0,0 +1,46 @@ +"use client"; + +import SupportStatusContext, { + SupporterStatus +} from "@app/contexts/supporterStatusContext"; +import { useState } from "react"; + +interface ProviderProps { + children: React.ReactNode; + supporterStatus: SupporterStatus | null; +} + +export function SupporterStatusProvider({ + children, + supporterStatus +}: ProviderProps) { + const [supporterStatusState, setSupporterStatusState] = + useState(supporterStatus); + + const updateSupporterStatus = ( + updatedSupporterStatus: Partial + ) => { + setSupporterStatusState((prev) => { + if (!prev) { + return updatedSupporterStatus as SupporterStatus; + } + return { + ...prev, + ...updatedSupporterStatus + }; + }); + }; + + return ( + + {children} + + ); +} + +export default SupporterStatusProvider;