From 2b54dfe035e46a55f0049efa6667b5f7d1dbb501 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 10 Mar 2025 12:47:42 -0400 Subject: [PATCH 01/12] fix rules info columns --- src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 From cdf904a2bc38f409a0b50c38810fc8e462fd2b97 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Sat, 15 Mar 2025 17:37:27 -0400 Subject: [PATCH 02/12] fix broken docs link closes #335 --- src/app/[orgId]/settings/resources/CreateResourceForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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({ From edba8186157f13dfda4ff412850e9ebecca254e8 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Sun, 16 Mar 2025 15:20:19 -0400 Subject: [PATCH 03/12] add new create site workflow --- install/config/config.yml | 2 +- package-lock.json | 2670 +++++++++++++++-- package.json | 3 + server/lib/consts.ts | 2 +- src/app/[orgId]/settings/layout.tsx | 20 +- src/app/[orgId]/settings/sites/SitesTable.tsx | 2 +- .../[orgId]/settings/sites/create/page.tsx | 861 ++++++ src/app/layout.tsx | 15 +- src/app/setup/page.tsx | 39 +- src/components/CopyTextBox.tsx | 10 +- src/components/CopyToClipboard.tsx | 20 +- src/components/Settings.tsx | 4 +- src/components/SettingsSectionTitle.tsx | 8 +- src/components/StrategySelect.tsx | 27 +- src/components/ui/button.tsx | 14 +- src/components/ui/table.tsx | 2 +- 16 files changed, 3416 insertions(+), 283 deletions(-) create mode 100644 src/app/[orgId]/settings/sites/create/page.tsx diff --git a/install/config/config.yml b/install/config/config.yml index 8b21b840..52be2d9e 100644 --- a/install/config/config.yml +++ b/install/config/config.yml @@ -41,7 +41,7 @@ gerbil: rate_limits: global: window_minutes: 1 - max_requests: 100 + max_requests: 500 {{if .EnableEmail}} email: smtp_host: "{{.EmailSMTPHost}}" diff --git a/package-lock.json b/package-lock.json index a1ee78cf..53d021df 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", @@ -9251,7 +9057,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=16" @@ -10014,6 +9819,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 +9988,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 +14232,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", @@ -14438,7 +16695,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^3.1.1" 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/lib/consts.ts b/server/lib/consts.ts index 2c35427a..90dc66a8 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.0.2"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); diff --git a/src/app/[orgId]/settings/layout.tsx b/src/app/[orgId]/settings/layout.tsx index b9912106..21a3b9c0 100644 --- a/src/app/[orgId]/settings/layout.tsx +++ b/src/app/[orgId]/settings/layout.tsx @@ -110,23 +110,19 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { return ( <> -
-
-
-
- -
- -
- +
+
+
+ +
+
+
-
- {children} -
+ {children}
); 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/layout.tsx b/src/app/layout.tsx index a892ccef..8a60bc42 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,7 +6,7 @@ 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"; export const metadata: Metadata = { @@ -46,9 +46,16 @@ export default async function RootLayout({ Pangolin
-
- Built by Fossorial -
+ + Fossorial + + )} - - {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} + )}
+ return
{children}
} export function SettingsSectionHeader({ children }: { children: React.ReactNode }) { - return
{children}
+ return
{children}
} export function SettingsSectionForm({ children }: { children: React.ReactNode }) { diff --git a/src/components/SettingsSectionTitle.tsx b/src/components/SettingsSectionTitle.tsx index ddd8b6fa..e7d9b7e9 100644 --- a/src/components/SettingsSectionTitle.tsx +++ b/src/components/SettingsSectionTitle.tsx @@ -1,13 +1,13 @@ type SettingsSectionTitleProps = { title: string | React.ReactNode; - description: string | React.ReactNode; + description?: string | React.ReactNode; size?: "2xl" | "1xl"; }; export default function SettingsSectionTitle({ title, description, - size, + size }: SettingsSectionTitleProps) { return (
{title} -

{description}

+ {description && ( +

{description}

+ )}
); } diff --git a/src/components/StrategySelect.tsx b/src/components/StrategySelect.tsx index 48c1fcb0..f36f13c9 100644 --- a/src/components/StrategySelect.tsx +++ b/src/components/StrategySelect.tsx @@ -2,42 +2,59 @@ import { cn } from "@app/lib/cn"; import { RadioGroup, RadioGroupItem } from "./ui/radio-group"; +import { useState } from "react"; interface StrategyOption { id: string; title: string; description: string; + disabled?: boolean; // New optional property } interface StrategySelectProps { options: StrategyOption[]; defaultValue?: string; onChange?: (value: string) => void; + cols?: number; } export function StrategySelect({ options, defaultValue, - onChange + onChange, + cols }: StrategySelectProps) { + const [selected, setSelected] = useState(defaultValue); + return ( { + setSelected(value); + onChange?.(value); + }} + className={`grid md:grid-cols-${cols ? cols : 1} gap-4`} > {options.map((option) => (
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< From af68aa692c554d011b971f448ed3713c3c2bdcf2 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 17 Mar 2025 11:53:30 -0400 Subject: [PATCH 04/12] fix header padding on large screens --- src/app/[orgId]/settings/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/[orgId]/settings/layout.tsx b/src/app/[orgId]/settings/layout.tsx index 21a3b9c0..2618c1fb 100644 --- a/src/app/[orgId]/settings/layout.tsx +++ b/src/app/[orgId]/settings/layout.tsx @@ -112,7 +112,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { <>
-
+
From 1c2ba4076a8784e89cbbf5baf9ed06ff98646fc1 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 19 Mar 2025 21:16:53 -0400 Subject: [PATCH 05/12] add crowdsec warning to installer --- install/main.go | 141 ++++++++++++++++++++++++------------------------ 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/install/main.go b/install/main.go index 9064b4f7..8fe56f5d 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?", true) { + 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())) +} From cdc415079cf5771d0e47deaab9d16d7c208dac42 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 20 Mar 2025 22:16:02 -0400 Subject: [PATCH 06/12] add supporer key program --- server/db/schema.ts | 10 + server/lib/config.ts | 101 ++++- server/routers/external.ts | 5 +- server/routers/internal.ts | 11 +- .../routers/supporterKey/hideSupporterKey.ts | 35 ++ server/routers/supporterKey/index.ts | 3 + .../supporterKey/isSupporterKeyVisible.ts | 54 +++ .../supporterKey/validateSupporterKey.ts | 115 ++++++ server/setup/migrations.ts | 4 +- server/setup/scripts/1.1.0.ts | 28 ++ .../[resourceId]/ResourceAuthPortal.tsx | 36 +- src/app/layout.tsx | 133 ++++--- src/components/Header.tsx | 9 +- src/components/SupporterStatus.tsx | 364 ++++++++++++++++++ src/contexts/supporterStatusContext.ts | 16 + src/hooks/useSupporterStatusContext.ts | 12 + src/providers/SupporterStatusProvider.tsx | 46 +++ 17 files changed, 908 insertions(+), 74 deletions(-) create mode 100644 server/routers/supporterKey/hideSupporterKey.ts create mode 100644 server/routers/supporterKey/index.ts create mode 100644 server/routers/supporterKey/isSupporterKeyVisible.ts create mode 100644 server/routers/supporterKey/validateSupporterKey.ts create mode 100644 server/setup/scripts/1.1.0.ts create mode 100644 src/components/SupporterStatus.tsx create mode 100644 src/contexts/supporterStatusContext.ts create mode 100644 src/hooks/useSupporterStatusContext.ts create mode 100644 src/providers/SupporterStatusProvider.tsx diff --git a/server/db/schema.ts b/server/db/schema.ts index 3d5f234f..02fe02eb 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -405,6 +405,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; @@ -439,3 +448,4 @@ export type ResourceWhitelist = InferSelectModel; export type VersionMigration = InferSelectModel; export type ResourceRule = InferSelectModel; export type Domain = InferSelectModel; +export type SupporterKey = InferSelectModel; diff --git a/server/lib/config.ts b/server/lib/config.ts index a04285d3..3f2902cc 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -10,6 +10,8 @@ import { } from "@server/lib/consts"; import { passwordSchema } from "@server/auth/passwordSchema"; import stoi from "./stoi"; +import db from "@server/db"; +import { SupporterKey, supporterKey } from "@server/db/schema"; const portSchema = z.number().positive().gt(0).lte(65535); @@ -155,6 +157,10 @@ const configSchema = z.object({ export class Config { private rawConfig!: z.infer; + supporterData: SupporterKey | null = null; + + supporterHiddenUntil: number | null = null; + constructor() { this.loadConfig(); } @@ -183,7 +189,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) { @@ -235,6 +243,17 @@ export class Config { : "false"; process.env.DASHBOARD_URL = parsedConfig.data.app.dashboard_url; + try { + this.checkSupporterKey(); + } catch (error) { + console.error("Error checking supporter key:", error); + } + + if (this.supporterData) { + process.env.SUPPORTER_DATA = JSON.stringify(this.supporterData); + console.log("Thank you for being a supporter of Pangolin!"); + } + this.rawConfig = parsedConfig.data; } @@ -251,6 +270,86 @@ 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, + valid: true + }; + + // update the supporter key in the database + await db.transaction(async (trx) => { + await trx.delete(supporterKey); + await trx.insert(supporterKey).values({ + githubUsername, + key: licenseKey, + tier: data.data.tier || null, + phrase: data.data.cutePhrase || null, + valid: true + }); + }); + } + + public getSupporterData() { + return this.supporterData; + } } export const config = new Config(); diff --git a/server/routers/external.ts b/server/routers/external.ts index f22fb281..c3f584c4 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -8,6 +8,7 @@ import * as target from "./target"; import * as user from "./user"; import * as auth from "./auth"; import * as role from "./role"; +import * as supporterKey from "./supporterKey"; import * as accessToken from "./accessToken"; import HttpCode from "@server/types/HttpCode"; import { @@ -239,7 +240,6 @@ authenticated.delete( target.deleteTarget ); - authenticated.put( "/org/:orgId/role", verifyOrgAccess, @@ -382,6 +382,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( diff --git a/server/routers/internal.ts b/server/routers/internal.ts index ead70d13..aaa955e6 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/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/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 8a60bc42..78217a50 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,6 +8,10 @@ import { Separator } from "@app/components/ui/separator"; import { pullEnv } from "@app/lib/pullEnv"; 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`, @@ -24,6 +28,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,65 +49,69 @@ export default async function RootLayout({ disableTransitionOnChange > - {/* Main content */} -
{children}
- - {/* Footer */} -
+ + {/* Footer */} + + diff --git a/src/components/Header.tsx b/src/components/Header.tsx index b3153220..f1823cc3 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -24,6 +24,7 @@ import { useRouter } from "next/navigation"; import { useState } from "react"; import { useUserContext } from "@app/hooks/useUserContext"; import ProfileIcon from "./ProfileIcon"; +import SupporterStatus from "./SupporterStatus"; type HeaderProps = { orgId?: string; @@ -42,7 +43,13 @@ export function Header({ orgId, orgs }: HeaderProps) { return ( <>
- +
+ + +
+ +
+
diff --git a/src/components/SupporterStatus.tsx b/src/components/SupporterStatus.tsx new file mode 100644 index 00000000..125c6522 --- /dev/null +++ b/src/components/SupporterStatus.tsx @@ -0,0 +1,364 @@ +"use client"; + +import Image from "next/image"; +import { Separator } from "@app/components/ui/separator"; +import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext"; +import { useState } from "react"; +import { + Popover, + PopoverContent, + PopoverTrigger +} from "@app/components/ui/popover"; +import { Button } from "./ui/button"; +import { + Credenza, + CredenzaBody, + CredenzaClose, + CredenzaContent, + CredenzaDescription, + CredenzaFooter, + CredenzaHeader, + CredenzaTitle +} from "./Credenza"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from "./ui/form"; +import { Input } from "./ui/input"; +import { toast } from "@app/hooks/useToast"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { AxiosResponse } from "axios"; +import { ValidateSupporterKeyResponse } from "@server/routers/supporterKey"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle +} from "./ui/card"; +import { Check, ExternalLink } from "lucide-react"; + +const formSchema = z.object({ + githubUsername: z + .string() + .nonempty({ message: "GitHub username is required" }), + key: z.string().nonempty({ message: "Supporter key is required" }) +}); + +export default function SupporterStatus() { + const { supporterStatus, updateSupporterStatus } = + useSupporterStatusContext(); + const [supportOpen, setSupportOpen] = useState(false); + const [keyOpen, setKeyOpen] = useState(false); + const [purchaseOptionsOpen, setPurchaseOptionsOpen] = useState(false); + + const api = createApiClient(useEnvContext()); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + githubUsername: "", + key: "" + } + }); + + async function hide() { + await api.post("/supporter-key/hide"); + + updateSupporterStatus({ + visible: false + }); + } + + async function onSubmit(values: z.infer) { + try { + const res = await api.post< + AxiosResponse + >("/supporter-key/validate", { + githubUsername: values.githubUsername, + key: values.key + }); + + const data = res.data.data; + + if (!data || !data.valid) { + toast({ + variant: "destructive", + title: "Invalid Key", + description: "Your supporter key is invalid." + }); + return; + } + + toast({ + variant: "default", + title: "Valid Key", + description: + "Your supporter key has been validated. Thank you for your support!" + }); + + setPurchaseOptionsOpen(false); + setKeyOpen(false); + + updateSupporterStatus({ + visible: false + }); + } catch (error) { + toast({ + variant: "destructive", + title: "Error", + description: formatAxiosError( + error, + "Failed to validate supporter key." + ) + }); + return; + } + } + + return ( + <> + { + setPurchaseOptionsOpen(val); + }} + > + + + + Support Development and Adopt a Pangolin! + + + +

+ Purchase a supporter key to help us continue + developing Pangolin. Your contribution allows us + commit more time to maintain and add new features to + the application for everyone. We will never use this + to paywall features. +

+ +

+ You will also get to adopt and meet your very own + pet Pangolin! +

+ +

+ Payments are processed via GitHub. Afterward, you + can retrieve your key on{" "} + + our website + {" "} + and redeem it here.{" "} + + Learn more. + +

+ +

Please select the option that best suits you.

+ +
+ + + Full Supporter + + +

$95

+
    +
  • + + + For the whole server + +
  • +
  • + + + Lifetime purchase + +
  • +
  • + + + Supporter status + +
  • +
+
+ + + + + +
+ + + + 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/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; From 06e90c95559f627c3d4eb0e17ac4230ab61d4187 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 20 Mar 2025 22:52:29 -0400 Subject: [PATCH 07/12] don't add wildcard asterisk for base domain resource closes #356 --- server/lib/consts.ts | 2 +- server/routers/traefik/getTraefikConfig.ts | 4 ++++ src/app/layout.tsx | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/server/lib/consts.ts b/server/lib/consts.ts index 90dc66a8..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.2"; +export const APP_VERSION = "1.1.0"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); 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/src/app/layout.tsx b/src/app/layout.tsx index 78217a50..225588c1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -18,6 +18,8 @@ export const metadata: Metadata = { description: "" }; +export const dynamic = 'force-dynamic'; + // const font = Figtree({ subsets: ["latin"] }); const font = Inter({ subsets: ["latin"] }); From 3b09ef3345487a4b1c0b37960372ce69fef08d9e Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 21 Mar 2025 09:57:28 -0400 Subject: [PATCH 08/12] False by default for crowdsec 2nd question --- install/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/main.go b/install/main.go index 8fe56f5d..47c846cf 100644 --- a/install/main.go +++ b/install/main.go @@ -99,7 +99,7 @@ func main() { // check if crowdsec is installed 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?", true) { + 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 { From deb30ed4ae068ebcd9f888aee0849969d82266e9 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 21 Mar 2025 17:05:04 -0400 Subject: [PATCH 09/12] Add admin user api interfaces --- server/middlewares/index.ts | 1 + server/middlewares/verifyUserIsServerAdmin.ts | 37 ++++++++ server/routers/external.ts | 10 +- server/routers/user/adminListUsers.ts | 92 +++++++++++++++++++ server/routers/user/adminRemoveUser.ts | 61 ++++++++++++ server/routers/user/index.ts | 4 +- server/routers/user/removeUserOrg.ts | 2 +- 7 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 server/middlewares/verifyUserIsServerAdmin.ts create mode 100644 server/routers/user/adminListUsers.ts create mode 100644 server/routers/user/adminRemoveUser.ts diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts index 03de18cb..b02f5b18 100644 --- a/server/middlewares/index.ts +++ b/server/middlewares/index.ts @@ -14,3 +14,4 @@ export * from "./verifyAdmin"; export * from "./verifySetResourceUsers"; export * from "./verifyUserInRole"; export * from "./verifyAccessTokenAccess"; +export * from "./verifyUserIsServerAdmin"; \ No newline at end of file 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 c3f584c4..2eeae9de 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -23,7 +23,8 @@ import { verifyRoleAccess, verifySetResourceUsers, verifyUserAccess, - getUserOrgs + getUserOrgs, + verifyUserIsServerAdmin } from "@server/middlewares"; import { verifyUserHasAction } from "../middlewares/verifyUserHasAction"; import { ActionsEnum } from "@server/auth/actions"; @@ -418,6 +419,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/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) { From 7131dea7a0644270873986a15de218aa8153b127 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 21 Mar 2025 17:12:59 -0400 Subject: [PATCH 10/12] package-lock update --- bruno/Users/adminListUsers.bru | 11 +++++++++++ bruno/Users/adminRemoveUser.bru | 11 +++++++++++ package-lock.json | 2 ++ 3 files changed, 24 insertions(+) create mode 100644 bruno/Users/adminListUsers.bru create mode 100644 bruno/Users/adminRemoveUser.bru 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/package-lock.json b/package-lock.json index 53d021df..c9e0a334 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9057,6 +9057,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=16" @@ -16695,6 +16696,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^3.1.1" From d72a8af04bf85b85e6f868f5ca203008d1717304 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 21 Mar 2025 17:13:41 -0400 Subject: [PATCH 11/12] log failed check key --- server/lib/config.ts | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/server/lib/config.ts b/server/lib/config.ts index 3f2902cc..42d442ab 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -12,6 +12,8 @@ import { passwordSchema } from "@server/auth/passwordSchema"; import stoi from "./stoi"; import db from "@server/db"; import { SupporterKey, supporterKey } from "@server/db/schema"; +import { suppressDeprecationWarnings } from "moment"; +import { eq } from "drizzle-orm"; const portSchema = z.number().positive().gt(0).lte(65535); @@ -243,16 +245,13 @@ export class Config { : "false"; process.env.DASHBOARD_URL = parsedConfig.data.app.dashboard_url; - try { - this.checkSupporterKey(); - } catch (error) { - console.error("Error checking supporter key:", error); - } - - if (this.supporterData) { - process.env.SUPPORTER_DATA = JSON.stringify(this.supporterData); - console.log("Thank you for being a supporter of Pangolin!"); - } + this.checkSupporterKey() + .then(() => { + console.log("Supporter key checked"); + }) + .catch((error) => { + console.error("Error checking supporter key:", error); + }); this.rawConfig = parsedConfig.data; } @@ -331,20 +330,19 @@ export class Config { this.supporterData = { ...key, + tier: data.data.tier, valid: true }; // update the supporter key in the database - await db.transaction(async (trx) => { - await trx.delete(supporterKey); - await trx.insert(supporterKey).values({ - githubUsername, - key: licenseKey, + 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() { From dbfc8b51aabe69e1dc28b54436d4d4cbf823eedd Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 21 Mar 2025 17:18:41 -0400 Subject: [PATCH 12/12] Update default email --- bruno/Auth/login.bru | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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!" } }