diff --git a/.github/values.yaml b/.github/values.yaml deleted file mode 100644 index 1b3ffd87a..000000000 --- a/.github/values.yaml +++ /dev/null @@ -1,57 +0,0 @@ -## @section Common parameters -## - -## @param nameOverride Override release name -## -nameOverride: "" -## @param fullnameOverride Override release fullname -## -fullnameOverride: "" - -## @section Infisical backend parameters -## Documentation : https://infisical.com/docs/self-hosting/deployments/kubernetes -## - -infisical: - autoDatabaseSchemaMigration: false - - enabled: false - - name: infisical - replicaCount: 3 - image: - repository: infisical/staging_infisical - tag: "latest" - pullPolicy: Always - - deploymentAnnotations: - secrets.infisical.com/auto-reload: "true" - - kubeSecretRef: "managed-secret" - -ingress: - ## @param ingress.enabled Enable ingress - ## - enabled: true - ## @param ingress.ingressClassName Ingress class name - ## - ingressClassName: nginx - ## @param ingress.nginx.enabled Ingress controller - ## - # nginx: - # enabled: true - ## @param ingress.annotations Ingress annotations - ## - annotations: - cert-manager.io/cluster-issuer: "letsencrypt-prod" - hostName: "gamma.infisical.com" - tls: - - secretName: letsencrypt-prod - hosts: - - gamma.infisical.com - -postgresql: - enabled: false - -redis: - enabled: false diff --git a/.github/workflows/helm-release-infisical-core.yml b/.github/workflows/helm-release-infisical-core.yml index 6c317cc27..49118a5ae 100644 --- a/.github/workflows/helm-release-infisical-core.yml +++ b/.github/workflows/helm-release-infisical-core.yml @@ -56,7 +56,7 @@ jobs: --config ct.yaml \ --charts helm-charts/infisical-standalone-postgres \ --helm-extra-args="--timeout=300s" \ - --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres" \ + --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.151.0" \ --namespace infisical-standalone-postgres release: diff --git a/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml b/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml index d48562fc6..f960aa6ff 100644 --- a/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml +++ b/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml @@ -66,5 +66,5 @@ jobs: --config ct.yaml \ --charts helm-charts/infisical-standalone-postgres \ --helm-extra-args="--timeout=300s" \ - --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres --set infisical.autoBootstrap.enabled=true" \ + --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.151.0 --set infisical.autoBootstrap.enabled=true" \ --namespace infisical-standalone-postgres diff --git a/backend/package-lock.json b/backend/package-lock.json index adda83043..a6cef3888 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -150,7 +150,7 @@ "@types/jsrp": "^0.2.6", "@types/libsodium-wrappers": "^0.7.13", "@types/lodash.isequal": "^4.5.8", - "@types/node": "^20.17.30", + "@types/node": "^20.19.0", "@types/nodemailer": "^6.4.14", "@types/passport-google-oauth20": "^2.0.14", "@types/pg": "^8.10.9", @@ -182,10 +182,10 @@ "ts-node": "^10.9.2", "tsc-alias": "^1.8.8", "tsconfig-paths": "^4.2.0", - "tsup": "^8.0.1", + "tsup": "^8.5.0", "tsx": "^4.4.0", "typescript": "^5.3.2", - "vitest": "^1.2.2" + "vitest": "^3.0.6" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -7451,9 +7451,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", "cpu": [ "ppc64" ], @@ -7464,269 +7464,285 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", "cpu": [ "arm64" ], @@ -7741,25 +7757,26 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", "cpu": [ "arm64" ], @@ -7774,19 +7791,20 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/openharmony-arm64": { @@ -7807,67 +7825,71 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -9280,18 +9302,6 @@ "node": ">=12" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -13245,9 +13255,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", "cpu": [ "arm" ], @@ -13259,9 +13269,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", "cpu": [ "arm64" ], @@ -13273,9 +13283,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", "cpu": [ "arm64" ], @@ -13287,9 +13297,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", "cpu": [ "x64" ], @@ -13300,10 +13310,38 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", "cpu": [ "arm" ], @@ -13315,9 +13353,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", "cpu": [ "arm" ], @@ -13329,9 +13367,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", "cpu": [ "arm64" ], @@ -13343,9 +13381,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", "cpu": [ "arm64" ], @@ -13356,10 +13394,24 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", "cpu": [ "ppc64" ], @@ -13371,9 +13423,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", "cpu": [ "riscv64" ], @@ -13385,9 +13451,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", "cpu": [ "s390x" ], @@ -13399,9 +13465,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", "cpu": [ "x64" ], @@ -13413,9 +13479,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", - "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", "cpu": [ "x64" ], @@ -13426,10 +13492,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", "cpu": [ "arm64" ], @@ -13441,9 +13521,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", "cpu": [ "ia32" ], @@ -13454,10 +13534,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", "cpu": [ "x64" ], @@ -13582,12 +13676,6 @@ "split2": "^4.0.0" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, "node_modules/@sindresorhus/slugify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-1.1.0.tgz", @@ -14944,6 +15032,17 @@ "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", "license": "MIT" }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/command-line-args": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", @@ -14983,6 +15082,13 @@ "@types/ms": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -15006,9 +15112,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -15147,12 +15253,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", - "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", + "version": "20.19.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz", + "integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==", "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.21.0" } }, "node_modules/@types/node-fetch": { @@ -15165,6 +15271,12 @@ "form-data": "^4.0.0" } }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, "node_modules/@types/nodemailer": { "version": "6.4.14", "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.14.tgz", @@ -15904,96 +16016,88 @@ "dev": true }, "node_modules/@vitest/expect": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz", - "integrity": "sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "1.2.2", - "@vitest/utils": "1.2.2", - "chai": "^4.3.10" + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.2.tgz", - "integrity": "sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "1.2.2", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@vitest/snapshot": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.2.tgz", - "integrity": "sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, + "license": "MIT", "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.2.tgz", - "integrity": "sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, + "license": "MIT", "dependencies": { - "tinyspy": "^2.2.0" + "tinyspy": "^4.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.2.tgz", - "integrity": "sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, + "license": "MIT", "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -17030,12 +17134,13 @@ } }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/async": { @@ -17931,10 +18036,11 @@ } }, "node_modules/bundle-require": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-4.0.2.tgz", - "integrity": "sha512-jwzPOChofl67PSTW2SGubV9HBQAhhR2i6nskiOThauo9dzwDUgOWQScFVaJkjEfYX+UXiD+LEx8EblQMc2wIag==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", "dev": true, + "license": "MIT", "dependencies": { "load-tsconfig": "^0.2.3" }, @@ -17942,7 +18048,7 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "peerDependencies": { - "esbuild": ">=0.17" + "esbuild": ">=0.18" } }, "node_modules/bytes": { @@ -18189,21 +18295,20 @@ } }, "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, + "license": "MIT", "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=18" } }, "node_modules/chalk": { @@ -18270,15 +18375,13 @@ } }, "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/chokidar": { @@ -19056,13 +19159,11 @@ } }, "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, + "license": "MIT", "engines": { "node": ">=6" } @@ -19297,15 +19398,6 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -19837,40 +19929,62 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/escalade": { @@ -20405,6 +20519,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -20478,6 +20593,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exponential-backoff": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", @@ -20867,6 +20992,24 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -21004,6 +21147,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -21534,15 +21689,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -21596,18 +21742,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -21625,10 +21759,11 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "dev": true, + "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -22343,15 +22478,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -22902,18 +23028,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -23392,12 +23506,6 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -23809,12 +23917,16 @@ "license": "MIT" }, "node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/limiter": { @@ -23856,6 +23968,7 @@ "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -23870,22 +23983,6 @@ "node": ">=6.11.5" } }, - "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", - "dev": true, - "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/localforage": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", @@ -24035,13 +24132,11 @@ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/lru-cache": { "version": "6.0.0", @@ -24072,15 +24167,13 @@ } }, "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/make-dir": { @@ -24513,15 +24606,16 @@ "dev": true }, "node_modules/mlly": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", - "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", "dev": true, + "license": "MIT", "dependencies": { - "acorn": "^8.10.0", - "pathe": "^1.1.1", - "pkg-types": "^1.0.3", - "ufo": "^1.3.0" + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" } }, "node_modules/mnemonist": { @@ -24828,9 +24922,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -25279,33 +25373,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -25355,13 +25422,6 @@ "node": "^14.16.0 || >=16.10.0" } }, - "node_modules/nypm/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/nypm/node_modules/pkg-types": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", @@ -27958,18 +28018,20 @@ } }, "node_modules/pathe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", - "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/pause": { @@ -28382,16 +28444,24 @@ } }, "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "dev": true, + "license": "MIT", "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, "node_modules/pkijs": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.2.4.tgz", @@ -28430,9 +28500,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -28450,8 +28520,8 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -28725,32 +28795,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/prism-react-renderer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", @@ -29339,397 +29383,6 @@ "node": ">=18.0.0" } }, - "node_modules/react-email/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/react-email/node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/react-email/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -29772,47 +29425,6 @@ "node": ">=18" } }, - "node_modules/react-email/node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } - }, "node_modules/react-email/node_modules/glob": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", @@ -30104,12 +29716,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/react-promise-suspense": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz", @@ -30609,13 +30215,13 @@ } }, "node_modules/rollup": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", - "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -30625,22 +30231,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.0", - "@rollup/rollup-android-arm64": "4.24.0", - "@rollup/rollup-darwin-arm64": "4.24.0", - "@rollup/rollup-darwin-x64": "4.24.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", - "@rollup/rollup-linux-arm-musleabihf": "4.24.0", - "@rollup/rollup-linux-arm64-gnu": "4.24.0", - "@rollup/rollup-linux-arm64-musl": "4.24.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", - "@rollup/rollup-linux-riscv64-gnu": "4.24.0", - "@rollup/rollup-linux-s390x-gnu": "4.24.0", - "@rollup/rollup-linux-x64-gnu": "4.24.0", - "@rollup/rollup-linux-x64-musl": "4.24.0", - "@rollup/rollup-win32-arm64-msvc": "4.24.0", - "@rollup/rollup-win32-ia32-msvc": "4.24.0", - "@rollup/rollup-win32-x64-msvc": "4.24.0", + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" } }, @@ -31145,7 +30757,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "3.0.7", @@ -31829,7 +31442,8 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/stacktrace-parser": { "version": "0.1.11", @@ -31868,10 +31482,11 @@ } }, "node_modules/std-env": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.6.0.tgz", - "integrity": "sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg==", - "dev": true + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" }, "node_modules/stdin-discarder": { "version": "0.1.0", @@ -32104,18 +31719,6 @@ "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -32128,17 +31731,25 @@ } }, "node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", "dev": true, + "license": "MIT", "dependencies": { - "acorn": "^8.10.0" + "js-tokens": "^9.0.1" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", @@ -32175,14 +31786,15 @@ } }, "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "7.1.6", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", @@ -32193,7 +31805,17 @@ "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/sucrase/node_modules/commander": { @@ -32206,25 +31828,68 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "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" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sucrase/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": 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/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -32708,10 +32373,11 @@ } }, "node_modules/tinybench": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", - "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", - "dev": true + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" }, "node_modules/tinyexec": { "version": "0.3.2", @@ -32720,20 +32386,62 @@ "dev": true, "license": "MIT" }, - "node_modules/tinypool": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", - "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -32995,24 +32703,28 @@ "license": "0BSD" }, "node_modules/tsup": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.0.1.tgz", - "integrity": "sha512-hvW7gUSG96j53ZTSlT4j/KL0q1Q2l6TqGBFc6/mu/L46IoNWqLLUzLRLP1R8Q7xrJTmkDxxDoojV5uCVs1sVOg==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.0.tgz", + "integrity": "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==", "dev": true, + "license": "MIT", "dependencies": { - "bundle-require": "^4.0.0", - "cac": "^6.7.12", - "chokidar": "^3.5.1", - "debug": "^4.3.1", - "esbuild": "^0.19.2", - "execa": "^5.0.0", - "globby": "^11.0.3", - "joycon": "^3.0.1", - "postcss-load-config": "^4.0.1", + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.25.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", - "rollup": "^4.0.2", + "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", - "sucrase": "^3.20.3", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "bin": { @@ -33043,365 +32755,30 @@ } } }, - "node_modules/tsup/node_modules/@esbuild/android-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz", - "integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==", - "cpu": [ - "arm" - ], + "node_modules/tsup/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, - "optional": true, - "os": [ - "android" - ], + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/android-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz", - "integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/android-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz", - "integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", - "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/darwin-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz", - "integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz", - "integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/freebsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz", - "integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz", - "integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", - "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz", - "integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-loong64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz", - "integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-mips64el": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz", - "integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-ppc64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz", - "integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-riscv64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz", - "integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-s390x": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz", - "integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz", - "integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/netbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz", - "integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/openbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz", - "integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/sunos-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz", - "integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/win32-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz", - "integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/win32-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz", - "integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/win32-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz", - "integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/tsup/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -33412,94 +32789,61 @@ } } }, - "node_modules/tsup/node_modules/esbuild": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", - "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", + "node_modules/tsup/node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" }, "engines": { - "node": ">=12" + "node": ">= 18" }, - "optionalDependencies": { - "@esbuild/android-arm": "0.19.9", - "@esbuild/android-arm64": "0.19.9", - "@esbuild/android-x64": "0.19.9", - "@esbuild/darwin-arm64": "0.19.9", - "@esbuild/darwin-x64": "0.19.9", - "@esbuild/freebsd-arm64": "0.19.9", - "@esbuild/freebsd-x64": "0.19.9", - "@esbuild/linux-arm": "0.19.9", - "@esbuild/linux-arm64": "0.19.9", - "@esbuild/linux-ia32": "0.19.9", - "@esbuild/linux-loong64": "0.19.9", - "@esbuild/linux-mips64el": "0.19.9", - "@esbuild/linux-ppc64": "0.19.9", - "@esbuild/linux-riscv64": "0.19.9", - "@esbuild/linux-s390x": "0.19.9", - "@esbuild/linux-x64": "0.19.9", - "@esbuild/netbsd-x64": "0.19.9", - "@esbuild/openbsd-x64": "0.19.9", - "@esbuild/sunos-x64": "0.19.9", - "@esbuild/win32-arm64": "0.19.9", - "@esbuild/win32-ia32": "0.19.9", - "@esbuild/win32-x64": "0.19.9" + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/tsup/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/tsup/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/tsup/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tsup/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/tsup/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/tsup/node_modules/resolve-from": { @@ -33523,15 +32867,6 @@ "node": ">= 8" } }, - "node_modules/tsup/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/tsup/node_modules/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -33559,13 +32894,14 @@ } }, "node_modules/tsx": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.4.0.tgz", - "integrity": "sha512-4fwcEjRUxW20ciSaMB8zkpGwCPxuRGnadDuj/pBk5S9uT29zvWz15PK36GrKJo45mSJomDxVejZ73c6lr3811Q==", + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "~0.18.20", - "get-tsconfig": "^4.7.2" + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" }, "bin": { "tsx": "dist/cli.mjs" @@ -33646,15 +32982,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -33767,10 +33094,11 @@ } }, "node_modules/ufo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", - "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", - "dev": true + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" }, "node_modules/uglify-js": { "version": "3.17.4", @@ -34155,22 +33483,100 @@ "node": ">=0.6.0" } }, - "node_modules/vite": { - "version": "5.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz", - "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==", + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/@types/node": { + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vite-node/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vite-node/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/vite-node/node_modules/vite": { + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -34179,19 +33585,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -34212,509 +33624,61 @@ }, "terser": { "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.2.tgz", - "integrity": "sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==", - "dev": true, - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite-node/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { + }, + "tsx": { + "optional": true + }, + "yaml": { "optional": true } } }, - "node_modules/vite-node/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, "node_modules/vitest": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz", - "integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/expect": "1.2.2", - "@vitest/runner": "1.2.2", - "@vitest/snapshot": "1.2.2", - "@vitest/spy": "1.2.2", - "@vitest/utils": "1.2.2", - "acorn-walk": "^8.3.2", - "cac": "^6.7.14", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^1.3.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.2", - "vite": "^5.0.0", - "vite-node": "1.2.2", - "why-is-node-running": "^2.2.2" + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "^1.0.0", - "@vitest/ui": "^1.0.0", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, @@ -34722,6 +33686,9 @@ "@edge-runtime/vm": { "optional": true }, + "@types/debug": { + "optional": true + }, "@types/node": { "optional": true }, @@ -34739,13 +33706,41 @@ } } }, - "node_modules/vitest/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -34756,93 +33751,92 @@ } } }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/vitest/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vitest/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/vitest/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/vitest/node_modules/vite": { + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^4.0.0" + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" + "url": "https://github.com/vitejs/vite?sponsor=1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, "node_modules/w3c-xmlserializer": { @@ -34944,13 +33938,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -35061,10 +34048,11 @@ } }, "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -35455,11 +34443,15 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yargs": { diff --git a/backend/package.json b/backend/package.json index 17be524f2..9bcd63cf1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -40,10 +40,10 @@ "type:check": "node --max-old-space-size=8192 ./node_modules/.bin/tsc --noEmit", "lint:fix": "node --max-old-space-size=8192 ./node_modules/.bin/eslint --fix --ext js,ts ./src", "lint": "node --max-old-space-size=8192 ./node_modules/.bin/eslint 'src/**/*.ts'", - "test:unit": "vitest run -c vitest.unit.config.ts", - "test:e2e": "vitest run -c vitest.e2e.config.ts --bail=1", - "test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1", - "test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts", + "test:unit": "vitest run -c vitest.unit.config.mts", + "test:e2e": "vitest run -c vitest.e2e.config.mts --bail=1", + "test:e2e-watch": "vitest -c vitest.e2e.config.mts --bail=1", + "test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.mts", "generate:component": "tsx ./scripts/create-backend-file.ts", "generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas", "auditlog-migration:latest": "node ./dist/db/rename-migrations-to-mjs.mjs && knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:latest", @@ -98,7 +98,7 @@ "@types/jsrp": "^0.2.6", "@types/libsodium-wrappers": "^0.7.13", "@types/lodash.isequal": "^4.5.8", - "@types/node": "^20.17.30", + "@types/node": "^20.19.0", "@types/nodemailer": "^6.4.14", "@types/passport-google-oauth20": "^2.0.14", "@types/pg": "^8.10.9", @@ -130,10 +130,10 @@ "ts-node": "^10.9.2", "tsc-alias": "^1.8.8", "tsconfig-paths": "^4.2.0", - "tsup": "^8.0.1", + "tsup": "^8.5.0", "tsx": "^4.4.0", "typescript": "^5.3.2", - "vitest": "^1.2.2" + "vitest": "^3.0.6" }, "dependencies": { "@aws-sdk/client-elasticache": "^3.637.0", diff --git a/backend/src/db/migrations/20251021112356_add-certificate-auto-renewal.ts b/backend/src/db/migrations/20251021112356_add-certificate-auto-renewal.ts new file mode 100644 index 000000000..583ffec90 --- /dev/null +++ b/backend/src/db/migrations/20251021112356_add-certificate-auto-renewal.ts @@ -0,0 +1,51 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + if (await knex.schema.hasColumn(TableName.PkiApiEnrollmentConfig, "autoRenewDays")) { + await knex.schema.alterTable(TableName.PkiApiEnrollmentConfig, (t) => { + t.renameColumn("autoRenewDays", "renewBeforeDays"); + }); + } + + if (!(await knex.schema.hasColumn(TableName.Certificate, "renewBeforeDays"))) { + await knex.schema.alterTable(TableName.Certificate, (t) => { + t.integer("renewBeforeDays").nullable(); + t.uuid("renewedFromCertificateId").nullable(); + t.uuid("renewedByCertificateId").nullable(); + t.text("renewalError").nullable(); + t.string("keyAlgorithm").nullable(); + t.string("signatureAlgorithm").nullable(); + t.foreign("renewedFromCertificateId").references("id").inTable(TableName.Certificate).onDelete("SET NULL"); + t.foreign("renewedByCertificateId").references("id").inTable(TableName.Certificate).onDelete("SET NULL"); + t.index("renewedFromCertificateId"); + t.index("renewedByCertificateId"); + t.index("renewBeforeDays"); + }); + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasColumn(TableName.Certificate, "renewBeforeDays")) { + await knex.schema.alterTable(TableName.Certificate, (t) => { + t.dropForeign(["renewedFromCertificateId"]); + t.dropForeign(["renewedByCertificateId"]); + t.dropIndex("renewedFromCertificateId"); + t.dropIndex("renewedByCertificateId"); + t.dropIndex("renewBeforeDays"); + t.dropColumn("renewBeforeDays"); + t.dropColumn("renewedFromCertificateId"); + t.dropColumn("renewedByCertificateId"); + t.dropColumn("renewalError"); + t.dropColumn("keyAlgorithm"); + t.dropColumn("signatureAlgorithm"); + }); + } + + if (await knex.schema.hasColumn(TableName.PkiApiEnrollmentConfig, "renewBeforeDays")) { + await knex.schema.alterTable(TableName.PkiApiEnrollmentConfig, (t) => { + t.renameColumn("renewBeforeDays", "autoRenewDays"); + }); + } +} diff --git a/backend/src/db/schemas/certificates.ts b/backend/src/db/schemas/certificates.ts index 63122f662..8a3ae8f84 100644 --- a/backend/src/db/schemas/certificates.ts +++ b/backend/src/db/schemas/certificates.ts @@ -27,7 +27,13 @@ export const CertificatesSchema = z.object({ extendedKeyUsages: z.string().array().nullable().optional(), projectId: z.string(), pkiSubscriberId: z.string().uuid().nullable().optional(), - profileId: z.string().uuid().nullable().optional() + profileId: z.string().uuid().nullable().optional(), + renewBeforeDays: z.number().nullable().optional(), + renewedFromCertificateId: z.string().uuid().nullable().optional(), + renewedByCertificateId: z.string().uuid().nullable().optional(), + renewalError: z.string().nullable().optional(), + keyAlgorithm: z.string().nullable().optional(), + signatureAlgorithm: z.string().nullable().optional() }); export type TCertificates = z.infer; diff --git a/backend/src/db/schemas/pki-api-enrollment-configs.ts b/backend/src/db/schemas/pki-api-enrollment-configs.ts index 710b0dee4..7a1beccdb 100644 --- a/backend/src/db/schemas/pki-api-enrollment-configs.ts +++ b/backend/src/db/schemas/pki-api-enrollment-configs.ts @@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models"; export const PkiApiEnrollmentConfigsSchema = z.object({ id: z.string().uuid(), autoRenew: z.boolean().default(false).nullable().optional(), - autoRenewDays: z.number().nullable().optional(), + renewBeforeDays: z.number().nullable().optional(), createdAt: z.date(), updatedAt: z.date() }); diff --git a/backend/src/ee/routes/v1/pam-account-routers/index.ts b/backend/src/ee/routes/v1/pam-account-routers/index.ts index 568412c84..60d621467 100644 --- a/backend/src/ee/routes/v1/pam-account-routers/index.ts +++ b/backend/src/ee/routes/v1/pam-account-routers/index.ts @@ -1,3 +1,8 @@ +import { + CreateMySQLAccountSchema, + SanitizedMySQLAccountWithResourceSchema, + UpdateMySQLAccountSchema +} from "@app/ee/services/pam-resource/mysql/mysql-resource-schemas"; import { PamResource } from "@app/ee/services/pam-resource/pam-resource-enums"; import { CreatePostgresAccountSchema, @@ -16,5 +21,14 @@ export const PAM_ACCOUNT_REGISTER_ROUTER_MAP: Record { + registerPamResourceEndpoints({ + server, + resourceType: PamResource.MySQL, + accountResponseSchema: SanitizedMySQLAccountWithResourceSchema, + createAccountSchema: CreateMySQLAccountSchema, + updateAccountSchema: UpdateMySQLAccountSchema + }); } }; diff --git a/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts b/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts index 647f39d8d..d2e0183ff 100644 --- a/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts +++ b/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { PamFoldersSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { SanitizedMySQLAccountWithResourceSchema } from "@app/ee/services/pam-resource/mysql/mysql-resource-schemas"; import { PamResource } from "@app/ee/services/pam-resource/pam-resource-enums"; import { SanitizedPostgresAccountWithResourceSchema } from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas"; import { BadRequestError } from "@app/lib/errors"; @@ -10,8 +11,10 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -// Use z.union([...]) when more resources are added -const SanitizedAccountSchema = SanitizedPostgresAccountWithResourceSchema; +const SanitizedAccountSchema = z.union([ + SanitizedPostgresAccountWithResourceSchema, + SanitizedMySQLAccountWithResourceSchema +]); export const registerPamAccountRouter = async (server: FastifyZodProvider) => { server.route({ diff --git a/backend/src/ee/routes/v1/pam-resource-routers/index.ts b/backend/src/ee/routes/v1/pam-resource-routers/index.ts index 6b53781ae..c6c0afcca 100644 --- a/backend/src/ee/routes/v1/pam-resource-routers/index.ts +++ b/backend/src/ee/routes/v1/pam-resource-routers/index.ts @@ -4,6 +4,11 @@ import { SanitizedPostgresResourceSchema, UpdatePostgresResourceSchema } from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas"; +import { + CreateMySQLResourceSchema, + MySQLResourceSchema, + UpdateMySQLResourceSchema +} from "@app/ee/services/pam-resource/mysql/mysql-resource-schemas"; import { registerPamResourceEndpoints } from "./pam-resource-endpoints"; @@ -16,5 +21,14 @@ export const PAM_RESOURCE_REGISTER_ROUTER_MAP: Record { + registerPamResourceEndpoints({ + server, + resourceType: PamResource.MySQL, + resourceResponseSchema: MySQLResourceSchema, + createResourceSchema: CreateMySQLResourceSchema, + updateResourceSchema: UpdateMySQLResourceSchema + }); } }; diff --git a/backend/src/ee/routes/v1/pam-resource-routers/pam-resource-router.ts b/backend/src/ee/routes/v1/pam-resource-routers/pam-resource-router.ts index d42a73021..6563c86c7 100644 --- a/backend/src/ee/routes/v1/pam-resource-routers/pam-resource-router.ts +++ b/backend/src/ee/routes/v1/pam-resource-routers/pam-resource-router.ts @@ -1,6 +1,10 @@ import { z } from "zod"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { + MySQLResourceListItemSchema, + SanitizedMySQLResourceSchema +} from "@app/ee/services/pam-resource/mysql/mysql-resource-schemas"; import { PostgresResourceListItemSchema, SanitizedPostgresResourceSchema @@ -9,10 +13,12 @@ import { readLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -// Use z.union([...]) when more resources are added -const SanitizedResourceSchema = SanitizedPostgresResourceSchema; +const SanitizedResourceSchema = z.union([SanitizedPostgresResourceSchema, SanitizedMySQLResourceSchema]); -const ResourceOptionsSchema = z.discriminatedUnion("resource", [PostgresResourceListItemSchema]); +const ResourceOptionsSchema = z.discriminatedUnion("resource", [ + PostgresResourceListItemSchema, + MySQLResourceListItemSchema +]); export const registerPamResourceRouter = async (server: FastifyZodProvider) => { server.route({ diff --git a/backend/src/ee/routes/v1/pam-session-router.ts b/backend/src/ee/routes/v1/pam-session-router.ts index c353fddfa..5fe10e434 100644 --- a/backend/src/ee/routes/v1/pam-session-router.ts +++ b/backend/src/ee/routes/v1/pam-session-router.ts @@ -2,14 +2,14 @@ import { z } from "zod"; import { PamSessionsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { MySQLSessionCredentialsSchema } from "@app/ee/services/pam-resource/mysql/mysql-resource-schemas"; import { PostgresSessionCredentialsSchema } from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas"; import { PamSessionCommandLogSchema, SanitizedSessionSchema } from "@app/ee/services/pam-session/pam-session-schemas"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -// Use z.union([]) once there's multiple -const SessionCredentialsSchema = PostgresSessionCredentialsSchema; +const SessionCredentialsSchema = z.union([PostgresSessionCredentialsSchema, MySQLSessionCredentialsSchema]); export const registerPamSessionRouter = async (server: FastifyZodProvider) => { // Meant to be hit solely by gateway identities diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 6ceaa7778..b58503110 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -340,6 +340,8 @@ export enum EventType { ISSUE_PKI_SUBSCRIBER_CERT = "issue-pki-subscriber-cert", SIGN_PKI_SUBSCRIBER_CERT = "sign-pki-subscriber-cert", AUTOMATED_RENEW_SUBSCRIBER_CERT = "automated-renew-subscriber-cert", + AUTOMATED_RENEW_CERTIFICATE = "automated-renew-certificate", + AUTOMATED_RENEW_CERTIFICATE_FAILED = "automated-renew-certificate-failed", LIST_PKI_SUBSCRIBER_CERTS = "list-pki-subscriber-certs", GET_SUBSCRIBER_ACTIVE_CERT_BUNDLE = "get-subscriber-active-cert-bundle", CREATE_KMS = "create-kms", @@ -367,6 +369,9 @@ export enum EventType { ISSUE_CERTIFICATE_FROM_PROFILE = "issue-certificate-from-profile", SIGN_CERTIFICATE_FROM_PROFILE = "sign-certificate-from-profile", ORDER_CERTIFICATE_FROM_PROFILE = "order-certificate-from-profile", + RENEW_CERTIFICATE = "renew-certificate", + UPDATE_CERTIFICATE_RENEWAL_CONFIG = "update-certificate-renewal-config", + DISABLE_CERTIFICATE_RENEWAL_CONFIG = "disable-certificate-renewal-config", ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration", ATTEMPT_REINSTALL_SLACK_INTEGRATION = "attempt-reinstall-slack-integration", GET_PROJECT_SLACK_CONFIG = "get-project-slack-config", @@ -2458,6 +2463,29 @@ interface AutomatedRenewPkiSubscriberCert { }; } +interface AutomatedRenewCertificate { + type: EventType.AUTOMATED_RENEW_CERTIFICATE; + metadata: { + certificateId: string; + commonName: string; + profileId: string; + renewBeforeDays: string; + profileName: string; + }; +} + +interface AutomatedRenewCertificateFailed { + type: EventType.AUTOMATED_RENEW_CERTIFICATE_FAILED; + metadata: { + certificateId: string; + commonName: string; + profileId: string; + renewBeforeDays: string; + profileName: string; + error: string; + }; +} + interface SignPkiSubscriberCert { type: EventType.SIGN_PKI_SUBSCRIBER_CERT; metadata: { @@ -2720,6 +2748,16 @@ interface OrderCertificateFromProfile { }; } +interface RenewCertificate { + type: EventType.RENEW_CERTIFICATE; + metadata: { + originalCertificateId: string; + newCertificateId: string; + profileName: string; + commonName: string; + }; +} + interface AttemptCreateSlackIntegration { type: EventType.ATTEMPT_CREATE_SLACK_INTEGRATION; metadata: { @@ -4009,6 +4047,23 @@ interface PamResourceDeleteEvent { }; } +interface UpdateCertificateRenewalConfigEvent { + type: EventType.UPDATE_CERTIFICATE_RENEWAL_CONFIG; + metadata: { + certificateId: string; + renewBeforeDays: string; + commonName: string; + }; +} + +interface DisableCertificateRenewalConfigEvent { + type: EventType.DISABLE_CERTIFICATE_RENEWAL_CONFIG; + metadata: { + certificateId: string; + commonName: string; + }; +} + export type Event = | CreateSubOrganizationEvent | UpdateSubOrganizationEvent @@ -4216,6 +4271,7 @@ export type Event = | IssueCertificateFromProfile | SignCertificateFromProfile | OrderCertificateFromProfile + | RenewCertificate | GetAzureAdCsTemplatesEvent | AttemptCreateSlackIntegration | AttemptReinstallSlackIntegration @@ -4373,4 +4429,8 @@ export type Event = | PamResourceGetEvent | PamResourceCreateEvent | PamResourceUpdateEvent - | PamResourceDeleteEvent; + | PamResourceDeleteEvent + | UpdateCertificateRenewalConfigEvent + | DisableCertificateRenewalConfigEvent + | AutomatedRenewCertificate + | AutomatedRenewCertificateFailed; diff --git a/backend/src/ee/services/pam-resource/mysql/mysql-resource-fns.ts b/backend/src/ee/services/pam-resource/mysql/mysql-resource-fns.ts new file mode 100644 index 000000000..4010d208b --- /dev/null +++ b/backend/src/ee/services/pam-resource/mysql/mysql-resource-fns.ts @@ -0,0 +1,8 @@ +import { MySQLResourceListItemSchema } from "./mysql-resource-schemas"; + +export const getMySQLResourceListItem = () => { + return { + name: MySQLResourceListItemSchema.shape.name.value, + resource: MySQLResourceListItemSchema.shape.resource.value + }; +}; diff --git a/backend/src/ee/services/pam-resource/mysql/mysql-resource-schemas.ts b/backend/src/ee/services/pam-resource/mysql/mysql-resource-schemas.ts new file mode 100644 index 000000000..8d3589a8a --- /dev/null +++ b/backend/src/ee/services/pam-resource/mysql/mysql-resource-schemas.ts @@ -0,0 +1,76 @@ +import { z } from "zod"; + +import { PamResource } from "../pam-resource-enums"; +import { + BaseCreatePamAccountSchema, + BaseCreatePamResourceSchema, + BasePamAccountSchema, + BasePamAccountSchemaWithResource, + BasePamResourceSchema, + BaseUpdatePamAccountSchema, + BaseUpdatePamResourceSchema +} from "../pam-resource-schemas"; +import { + BaseSqlAccountCredentialsSchema, + BaseSqlResourceConnectionDetailsSchema +} from "../shared/sql/sql-resource-schemas"; + +// Resources +export const MySQLResourceConnectionDetailsSchema = BaseSqlResourceConnectionDetailsSchema.extend({ + // MySQL db in many cases the db will not be provided when making connection + database: z.string().trim() +}); +export const MySQLAccountCredentialsSchema = BaseSqlAccountCredentialsSchema; + +const BaseMySQLResourceSchema = BasePamResourceSchema.extend({ resourceType: z.literal(PamResource.MySQL) }); + +export const MySQLResourceSchema = BaseMySQLResourceSchema.extend({ + connectionDetails: MySQLResourceConnectionDetailsSchema, + rotationAccountCredentials: MySQLAccountCredentialsSchema.nullable().optional() +}); + +export const SanitizedMySQLResourceSchema = BaseMySQLResourceSchema.extend({ + connectionDetails: MySQLResourceConnectionDetailsSchema, + rotationAccountCredentials: MySQLAccountCredentialsSchema.pick({ + username: true + }) + .nullable() + .optional() +}); + +export const MySQLResourceListItemSchema = z.object({ + name: z.literal("MySQL"), + resource: z.literal(PamResource.MySQL) +}); + +export const CreateMySQLResourceSchema = BaseCreatePamResourceSchema.extend({ + connectionDetails: MySQLResourceConnectionDetailsSchema, + rotationAccountCredentials: MySQLAccountCredentialsSchema.nullable().optional() +}); + +export const UpdateMySQLResourceSchema = BaseUpdatePamResourceSchema.extend({ + connectionDetails: MySQLResourceConnectionDetailsSchema.optional(), + rotationAccountCredentials: MySQLAccountCredentialsSchema.nullable().optional() +}); + +// Accounts +export const MySQLAccountSchema = BasePamAccountSchema.extend({ + credentials: MySQLAccountCredentialsSchema +}); + +export const CreateMySQLAccountSchema = BaseCreatePamAccountSchema.extend({ + credentials: MySQLAccountCredentialsSchema +}); + +export const UpdateMySQLAccountSchema = BaseUpdatePamAccountSchema.extend({ + credentials: MySQLAccountCredentialsSchema.optional() +}); + +export const SanitizedMySQLAccountWithResourceSchema = BasePamAccountSchemaWithResource.extend({ + credentials: MySQLAccountCredentialsSchema.pick({ + username: true + }) +}); + +// Sessions +export const MySQLSessionCredentialsSchema = MySQLResourceConnectionDetailsSchema.and(MySQLAccountCredentialsSchema); diff --git a/backend/src/ee/services/pam-resource/mysql/mysql-resource-types.ts b/backend/src/ee/services/pam-resource/mysql/mysql-resource-types.ts new file mode 100644 index 000000000..43c2eea51 --- /dev/null +++ b/backend/src/ee/services/pam-resource/mysql/mysql-resource-types.ts @@ -0,0 +1,16 @@ +import { z } from "zod"; + +import { + MySQLAccountCredentialsSchema, + MySQLAccountSchema, + MySQLResourceConnectionDetailsSchema, + MySQLResourceSchema +} from "./mysql-resource-schemas"; + +// Resources +export type TMySQLResource = z.infer; +export type TMySQLResourceConnectionDetails = z.infer; + +// Accounts +export type TMySQLAccount = z.infer; +export type TMySQLAccountCredentials = z.infer; diff --git a/backend/src/ee/services/pam-resource/pam-resource-enums.ts b/backend/src/ee/services/pam-resource/pam-resource-enums.ts index fbc260fba..dff1cc650 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-enums.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-enums.ts @@ -1,3 +1,4 @@ export enum PamResource { - Postgres = "postgres" + Postgres = "postgres", + MySQL = "mysql" } diff --git a/backend/src/ee/services/pam-resource/pam-resource-factory.ts b/backend/src/ee/services/pam-resource/pam-resource-factory.ts index 298b1664c..151fa7ea1 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-factory.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-factory.ts @@ -5,5 +5,6 @@ import { sqlResourceFactory } from "./shared/sql/sql-resource-factory"; type TPamResourceFactoryImplementation = TPamResourceFactory; export const PAM_RESOURCE_FACTORY_MAP: Record = { - [PamResource.Postgres]: sqlResourceFactory as TPamResourceFactoryImplementation + [PamResource.Postgres]: sqlResourceFactory as TPamResourceFactoryImplementation, + [PamResource.MySQL]: sqlResourceFactory as TPamResourceFactoryImplementation }; diff --git a/backend/src/ee/services/pam-resource/pam-resource-fns.ts b/backend/src/ee/services/pam-resource/pam-resource-fns.ts index 9d7493e68..cad087d2f 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-fns.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-fns.ts @@ -3,11 +3,12 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsDataKey } from "@app/services/kms/kms-types"; import { decryptAccountCredentials } from "../pam-account/pam-account-fns"; +import { getMySQLResourceListItem } from "./mysql/mysql-resource-fns"; import { TPamResource, TPamResourceConnectionDetails } from "./pam-resource-types"; import { getPostgresResourceListItem } from "./postgres/postgres-resource-fns"; export const listResourceOptions = () => { - return [getPostgresResourceListItem()].sort((a, b) => a.name.localeCompare(b.name)); + return [getPostgresResourceListItem(), getMySQLResourceListItem()].sort((a, b) => a.name.localeCompare(b.name)); }; // Resource diff --git a/backend/src/ee/services/pam-resource/pam-resource-types.ts b/backend/src/ee/services/pam-resource/pam-resource-types.ts index f2016420a..1ca9db3e2 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-types.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-types.ts @@ -1,4 +1,10 @@ import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service"; +import { + TMySQLAccount, + TMySQLAccountCredentials, + TMySQLResource, + TMySQLResourceConnectionDetails +} from "./mysql/mysql-resource-types"; import { PamResource } from "./pam-resource-enums"; import { TPostgresAccount, @@ -8,12 +14,13 @@ import { } from "./postgres/postgres-resource-types"; // Resource types -export type TPamResource = TPostgresResource; -export type TPamResourceConnectionDetails = TPostgresResourceConnectionDetails; +export type TPamResource = TPostgresResource | TMySQLResource; +export type TPamResourceConnectionDetails = TPostgresResourceConnectionDetails | TMySQLResourceConnectionDetails; // Account types -export type TPamAccount = TPostgresAccount; -export type TPamAccountCredentials = TPostgresAccountCredentials; +export type TPamAccount = TPostgresAccount | TMySQLAccount; +// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents +export type TPamAccountCredentials = TPostgresAccountCredentials | TMySQLAccountCredentials; // Resource DTOs export type TCreateResourceDTO = Pick< diff --git a/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts b/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts index 73defd6e6..a171b39b5 100644 --- a/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts +++ b/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts @@ -1,4 +1,6 @@ -import knex, { Knex } from "knex"; +import knex from "knex"; +import mysql, { Connection } from "mysql2/promise"; +import * as pg from "pg"; import tls, { PeerCertificate } from "tls"; import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns"; @@ -20,30 +22,160 @@ const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000; const TEST_CONNECTION_USERNAME = "infisical-gateway-connection-test"; const TEST_CONNECTION_PASSWORD = "infisical-gateway-connection-test-password"; +const SIMPLE_QUERY = "select 1"; -const SQL_CONNECTION_CLIENT_MAP = { - [PamResource.Postgres]: "pg" -}; +export interface SqlResourceConnection { + /** + * Check and see if the connection is good or not. + * + * @param connectOnly when true, if we only want to know that making the connection is possible or not, + * we don't care about authentication failures + * @returns Promise to be resolved when the connection is good, otherwise an error will be errbacked + */ + validate: (connectOnly: boolean) => Promise; -const getConnectionConfig = ( - resourceType: PamResource, - { host, sslEnabled, sslRejectUnauthorized, sslCertificate }: TSqlResourceConnectionDetails -) => { - switch (resourceType) { + /** + * Rotate password and return the new credentials. + * + * @param currentCredentials the current credentials to rotate + * + * @returns Promise to be resolved with the new credentials + */ + rotateCredentials: (currentCredentials: TSqlAccountCredentials) => Promise; + + /** + * Close the connection. + * + * @returns Promise for closing the connection + */ + close: () => Promise; +} + +const makeSqlConnection = ( + proxyPort: number, + config: { + connectionDetails: TSqlResourceConnectionDetails; + resourceType: PamResource; + username?: string; + password?: string; + } +): SqlResourceConnection => { + const { connectionDetails, resourceType, username, password } = config; + const { host, sslEnabled, sslRejectUnauthorized, sslCertificate } = connectionDetails; + const actualUsername = username ?? TEST_CONNECTION_USERNAME; // Use provided username or fallback + const actualPassword = password ?? TEST_CONNECTION_PASSWORD; // Use provided password or fallback + switch (config.resourceType) { case PamResource.Postgres: { + const client = knex({ + client: "pg", + connection: { + host: "localhost", + port: proxyPort, + user: actualUsername, + password: actualPassword, + database: connectionDetails.database, + connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT, + ssl: sslEnabled + ? { + rejectUnauthorized: sslRejectUnauthorized, + ca: sslCertificate, + servername: host, + // When using proxy, we need to bypass hostname validation since we connect to localhost + // but validate the certificate against the actual hostname + checkServerIdentity: (hostname: string, cert: PeerCertificate) => { + return tls.checkServerIdentity(host, cert); + } + } + : false + } + }); return { - ssl: sslEnabled - ? { - rejectUnauthorized: sslRejectUnauthorized, - ca: sslCertificate, - servername: host, - // When using proxy, we need to bypass hostname validation since we connect to localhost - // but validate the certificate against the actual hostname - checkServerIdentity: (hostname: string, cert: PeerCertificate) => { - return tls.checkServerIdentity(host, cert); + validate: async (connectOnly) => { + try { + await client.raw(SIMPLE_QUERY); + } catch (error) { + if (error instanceof pg.DatabaseError) { + // Hacky way to know if we successfully hit the database. + // TODO: potentially two approaches to solve the problem. + // 1. change the work flow, add account first then resource + // 2. modify relay to add a new endpoint for returning if the target host is healthy or not + // (like being able to do an auth handshake regardless pass or not) + if ( + connectOnly && + (error.message === `password authentication failed for user "${TEST_CONNECTION_USERNAME}"` || + error.message.includes("no pg_hba.conf entry for host")) + ) { + return; } } - : false + throw new BadRequestError({ + message: `Unable to validate connection to ${resourceType}: ${(error as Error).message || String(error)}` + }); + } + }, + rotateCredentials: async (currentCredentials) => { + const newPassword = alphaNumericNanoId(32); + // Note: The generated random password is not really going to make SQL Injection possible. + // The reason we are not using parameters binding is that the "ALTER USER" syntax is DDL, + // parameters binding is not supported. But just in case if the this code got copied + // around and repurposed, let's just do some naive escaping regardless + await client.raw(`ALTER USER :username: WITH PASSWORD '${newPassword.replace(/'/g, "''")}'`, { + username: currentCredentials.username + }); + return { username: currentCredentials.username, password: newPassword }; + }, + close: () => client.destroy() + }; + } + case PamResource.MySQL: { + return { + validate: async (connectOnly) => { + let client: Connection | null = null; + try { + // Notice: the reason we are not using Knex for mysql2 is because we don't need any fancy feature from Knex. + // mysql2 doesn't provide custom ssl verification function pass in. + // ref: https://github.com/sidorares/node-mysql2/blob/2543272a2ada8d8a07f74582549d7dd3fe948e2d/lib/base/connection.js#L358-L362 + // and then even I tried to workaround it with Knex's pool afterCreate hook, but then encounter a bug: + // ref: https://github.com/knex/knex/issues/5352 + // It appears that using Knex causing more troubles than not, we are just checking the connections, + // so it's much easier to create raw connection with the driver lib directly + client = await mysql.createConnection({ + host: "localhost", + port: proxyPort, + user: actualUsername, // Use provided username or fallback + password: actualPassword, // Use provided password or fallback + database: connectionDetails.database, + ssl: sslEnabled + ? { + rejectUnauthorized: sslRejectUnauthorized, + ca: sslCertificate + } + : undefined + }); + await client.query(SIMPLE_QUERY); + } catch (error) { + if (connectOnly) { + // Hacky way to know if we successfully hit the database. + if ( + error instanceof Error && + error.message.startsWith(`Access denied for user '${TEST_CONNECTION_USERNAME}'@`) + ) { + return; + } + } + // TODO: handle other errors, and throw standardlized errors providing user-friendly msg + throw error; + } finally { + await client?.end(); + } + }, + rotateCredentials: async () => { + // TODO: the pwd rotation for MySQL is not supported yet + throw new BadRequestError({ + message: "Unsupported operation" + }); + }, + close: async () => {} }; } default: @@ -62,10 +194,9 @@ export const executeWithGateway = async ( password?: string; }, gatewayV2Service: Pick, - operation: (client: Knex) => Promise + operation: (connection: SqlResourceConnection) => Promise ): Promise => { - const { connectionDetails, resourceType, gatewayId, username, password } = config; - + const { connectionDetails, gatewayId } = config; const [targetHost] = await verifyHostInputValidity(connectionDetails.host, true); const platformConnectionDetails = await gatewayV2Service.getPlatformConnectionDetailsByGatewayId({ gatewayId, @@ -79,22 +210,11 @@ export const executeWithGateway = async ( return withGatewayV2Proxy( async (proxyPort) => { - const client = knex({ - client: SQL_CONNECTION_CLIENT_MAP[resourceType], - connection: { - database: connectionDetails.database, - port: proxyPort, - host: "localhost", - user: username ?? TEST_CONNECTION_USERNAME, // Use provided username or fallback - password: password ?? TEST_CONNECTION_PASSWORD, // Use provided password or fallback - connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT, - ...getConnectionConfig(resourceType, connectionDetails) - } - }); + const connection = makeSqlConnection(proxyPort, config); try { - return await operation(client); + return await operation(connection); } finally { - await client.destroy(); + await connection.close(); } }, { @@ -115,25 +235,14 @@ export const sqlResourceFactory: TPamResourceFactory { try { await executeWithGateway({ connectionDetails, gatewayId, resourceType }, gatewayV2Service, async (client) => { - await client.raw("Select 1"); + await client.validate(true); }); return connectionDetails; } catch (error) { - // Hacky way to know if we successfully hit the database - if (error instanceof BadRequestError) { - if (error.message === `password authentication failed for user "${TEST_CONNECTION_USERNAME}"`) { - return connectionDetails; - } - - if (error.message.includes("no pg_hba.conf entry for host")) { - return connectionDetails; - } - - if (error.message === "Connection terminated unexpectedly") { - throw new BadRequestError({ - message: "Connection terminated unexpectedly. Verify that host and port are correct" - }); - } + if (error instanceof BadRequestError && error.message === "Connection terminated unexpectedly") { + throw new BadRequestError({ + message: "Connection terminated unexpectedly. Verify that host and port are correct" + }); } throw new BadRequestError({ @@ -156,11 +265,12 @@ export const sqlResourceFactory: TPamResourceFactory { - await client.raw("Select 1"); + await client.validate(false); } ); return credentials; } catch (error) { + // TODO: extract these logic into each SQL connection if (error instanceof BadRequestError) { if (error.message === `password authentication failed for user "${credentials.username}"`) { throw new BadRequestError({ @@ -186,9 +296,7 @@ export const sqlResourceFactory: TPamResourceFactory { try { - const newPassword = alphaNumericNanoId(32); - - await executeWithGateway( + return await executeWithGateway( { connectionDetails, gatewayId, @@ -197,20 +305,8 @@ export const sqlResourceFactory: TPamResourceFactory { - switch (resourceType) { - case PamResource.Postgres: - await client.raw(`ALTER USER ?? WITH PASSWORD '${newPassword}'`, [currentCredentials.username]); - break; - default: - throw new BadRequestError({ - message: `Password rotation for ${resourceType as PamResource} is not supported.` - }); - } - } + (client) => client.rotateCredentials(currentCredentials) ); - - return { username: currentCredentials.username, password: newPassword }; } catch (error) { if (error instanceof BadRequestError) { if (error.message === `password authentication failed for user "${rotationAccountCredentials.username}"`) { diff --git a/backend/src/ee/services/pam-resource/shared/sql/sql-resource-types.ts b/backend/src/ee/services/pam-resource/shared/sql/sql-resource-types.ts index f56a2a3dc..a9ff49013 100644 --- a/backend/src/ee/services/pam-resource/shared/sql/sql-resource-types.ts +++ b/backend/src/ee/services/pam-resource/shared/sql/sql-resource-types.ts @@ -1,7 +1,9 @@ +import { TMySQLAccountCredentials, TMySQLResourceConnectionDetails } from "../../mysql/mysql-resource-types"; import { TPostgresAccountCredentials, TPostgresResourceConnectionDetails } from "../../postgres/postgres-resource-types"; -export type TSqlResourceConnectionDetails = TPostgresResourceConnectionDetails; -export type TSqlAccountCredentials = TPostgresAccountCredentials; +export type TSqlResourceConnectionDetails = TPostgresResourceConnectionDetails | TMySQLResourceConnectionDetails; +// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents +export type TSqlAccountCredentials = TPostgresAccountCredentials | TMySQLAccountCredentials; diff --git a/backend/src/queue/queue-service.ts b/backend/src/queue/queue-service.ts index 9d8c472f4..3e6b1dd19 100644 --- a/backend/src/queue/queue-service.ts +++ b/backend/src/queue/queue-service.ts @@ -78,6 +78,7 @@ export enum QueueName { SecretReminderMigration = "secret-reminder-migration", UserNotification = "user-notification", HealthAlert = "health-alert", + CertificateV3AutoRenewal = "certificate-v3-auto-renewal", PamAccountRotation = "pam-account-rotation" } @@ -128,6 +129,7 @@ export enum QueueJobs { SecretReminderMigration = "secret-reminder-migration", UserNotification = "user-notification-job", HealthAlert = "health-alert", + CertificateV3DailyAutoRenewal = "certificate-v3-daily-auto-renewal", PamAccountRotation = "pam-account-rotation" } @@ -359,6 +361,10 @@ export type TQueueJobTypes = { name: QueueJobs.HealthAlert; payload: undefined; }; + [QueueName.CertificateV3AutoRenewal]: { + name: QueueJobs.CertificateV3DailyAutoRenewal; + payload: undefined; + }; [QueueName.PamAccountRotation]: { name: QueueJobs.PamAccountRotation; payload: undefined; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index b42d01850..c135db83e 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -177,6 +177,7 @@ import { certificateTemplateEstConfigDALFactory } from "@app/services/certificat import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service"; import { certificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal"; import { certificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; +import { certificateV3QueueServiceFactory } from "@app/services/certificate-v3/certificate-v3-queue"; import { certificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; import { cmekServiceFactory } from "@app/services/cmek/cmek-service"; import { convertorServiceFactory } from "@app/services/convertor/convertor-service"; @@ -2136,6 +2137,7 @@ export const registerRoutes = async ( const certificateV3Service = certificateV3ServiceFactory({ certificateDAL, + certificateSecretDAL, certificateAuthorityDAL, certificateProfileDAL, certificateTemplateV2Service, @@ -2143,6 +2145,13 @@ export const registerRoutes = async ( permissionService }); + const certificateV3Queue = certificateV3QueueServiceFactory({ + queueService, + certificateDAL, + certificateV3Service, + auditLogService + }); + const certificateEstV3Service = certificateEstV3ServiceFactory({ internalCertificateAuthorityService, certificateTemplateV2Service, @@ -2330,6 +2339,7 @@ export const registerRoutes = async ( await dailyReminderQueueService.startSecretReminderMigrationJob(); await dailyExpiringPkiItemAlert.startSendingAlerts(); await pkiSubscriberQueue.startDailyAutoRenewalJob(); + await certificateV3Queue.init(); await kmsService.startService(hsmStatus); await microsoftTeamsService.start(); await dynamicSecretQueueService.init(); diff --git a/backend/src/server/routes/v1/certificate-profiles-router.ts b/backend/src/server/routes/v1/certificate-profiles-router.ts index 2292c3ba8..af32bd6c7 100644 --- a/backend/src/server/routes/v1/certificate-profiles-router.ts +++ b/backend/src/server/routes/v1/certificate-profiles-router.ts @@ -42,7 +42,7 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid apiConfig: z .object({ autoRenew: z.boolean().default(false), - autoRenewDays: z.number().min(1).max(365).optional() + renewBeforeDays: z.number().min(1).max(30).optional() }) .optional() }) @@ -150,7 +150,7 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid .object({ id: z.string(), autoRenew: z.boolean(), - autoRenewDays: z.number().optional() + renewBeforeDays: z.number().optional() }) .optional() }).array(), @@ -230,7 +230,7 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid .object({ id: z.string(), autoRenew: z.boolean(), - autoRenewDays: z.number().optional() + renewBeforeDays: z.number().optional() }) .optional(), metrics: z @@ -355,7 +355,7 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid apiConfig: z .object({ autoRenew: z.boolean().default(false), - autoRenewDays: z.number().min(1).max(365).optional() + renewBeforeDays: z.number().min(1).max(30).optional() }) .optional() }) diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index c1f4140e5..c3bffa2fc 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -1200,7 +1200,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - certificates: z.array(CertificatesSchema), + certificates: z.array(CertificatesSchema.extend({ hasPrivateKey: z.boolean() })), totalCount: z.number() }) } diff --git a/backend/src/server/routes/v3/certificates-router.ts b/backend/src/server/routes/v3/certificates-router.ts index 549310738..d2d696596 100644 --- a/backend/src/server/routes/v3/certificates-router.ts +++ b/backend/src/server/routes/v3/certificates-router.ts @@ -18,6 +18,7 @@ import { CertKeyUsageType, CertSubjectAlternativeNameType } from "@app/services/certificate-common/certificate-constants"; +import { extractCertificateRequestFromCSR } from "@app/services/certificate-common/certificate-csr-utils"; import { mapEnumsForValidation } from "@app/services/certificate-common/certificate-utils"; import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators"; @@ -84,8 +85,8 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) => }) ) .optional(), - signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(), - keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional() + signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm), + keyAlgorithm: z.nativeEnum(CertKeyAlgorithm) }) .refine(validateTtlAndDateFields, { message: @@ -169,9 +170,7 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) => .min(1, "TTL cannot be empty") .refine((val) => ms(val) > 0, "TTL must be a positive number"), notBefore: validateCaDateField.optional(), - notAfter: validateCaDateField.optional(), - signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(), - keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional() + notAfter: validateCaDateField.optional() }) .refine(validateTtlAndDateFields, { message: @@ -192,6 +191,8 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) => }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { + const certificateRequest = extractCertificateRequestFromCSR(req.body.csr); + const data = await server.services.certificateV3.signCertificateFromProfile({ actor: req.permission.type, actorId: req.permission.id, @@ -203,9 +204,7 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) => ttl: req.body.ttl }, notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined, - notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined, - signatureAlgorithm: req.body.signatureAlgorithm, - keyAlgorithm: req.body.keyAlgorithm + notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined }); await server.services.auditLog.createAuditLog({ @@ -217,7 +216,7 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) => certificateProfileId: req.body.profileId, certificateId: data.certificateId, profileName: data.profileName, - commonName: "" + commonName: certificateRequest.commonName || "" } } }); @@ -260,8 +259,8 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) => notBefore: validateCaDateField.optional(), notAfter: validateCaDateField.optional(), commonName: validateTemplateRegexField.optional(), - signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(), - keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional() + signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm), + keyAlgorithm: z.nativeEnum(CertKeyAlgorithm) }) .refine(validateTtlAndDateFields, { message: @@ -343,4 +342,145 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) => return data; } }); + + server.route({ + method: "POST", + url: "/:certificateId/renew", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificates], + params: z.object({ + certificateId: z.string().uuid() + }), + response: { + 200: z.object({ + certificate: z.string().trim(), + issuingCaCertificate: z.string().trim(), + certificateChain: z.string().trim(), + privateKey: z.string().trim().optional(), + serialNumber: z.string().trim(), + certificateId: z.string() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const data = await server.services.certificateV3.renewCertificate({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + certificateId: req.params.certificateId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: data.projectId, + event: { + type: EventType.RENEW_CERTIFICATE, + metadata: { + originalCertificateId: req.params.certificateId, + newCertificateId: data.certificateId, + profileName: data.profileName, + commonName: data.commonName + } + } + }); + + return data; + } + }); + + server.route({ + method: "PATCH", + url: "/:certificateId/config", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificates], + params: z.object({ + certificateId: z.string().uuid() + }), + body: z + .object({ + renewBeforeDays: z.number().int().min(1).max(30).optional(), + enableAutoRenewal: z.boolean().optional() + }) + .refine((data) => !(data.renewBeforeDays !== undefined && data.enableAutoRenewal === false), { + message: "Cannot specify both renewBeforeDays and enableAutoRenewal=false" + }), + response: { + 200: z.object({ + message: z.string(), + renewBeforeDays: z.number().optional() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + if (req.body.enableAutoRenewal === false) { + const data = await server.services.certificateV3.disableRenewalConfig({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + certificateId: req.params.certificateId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: data.projectId, + event: { + type: EventType.DISABLE_CERTIFICATE_RENEWAL_CONFIG, + metadata: { + certificateId: req.params.certificateId, + commonName: data.commonName + } + } + }); + + return { + message: "Auto-renewal disabled successfully" + }; + } + + if (req.body.renewBeforeDays !== undefined) { + const data = await server.services.certificateV3.updateRenewalConfig({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + certificateId: req.params.certificateId, + renewBeforeDays: req.body.renewBeforeDays + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: data.projectId, + event: { + type: EventType.UPDATE_CERTIFICATE_RENEWAL_CONFIG, + metadata: { + certificateId: req.params.certificateId, + renewBeforeDays: req.body.renewBeforeDays.toString(), + commonName: data.commonName + } + } + }); + + return { + message: "Certificate configuration updated successfully", + renewBeforeDays: data.renewBeforeDays + }; + } + + return { + message: "No configuration changes requested" + }; + } + }); }; diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index 464f718ce..21989c6ae 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -345,6 +345,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) => case GitHubConnectionMethod.App: case GitHubRadarConnectionMethod.App: return "GitHub App"; + case GitHubConnectionMethod.Pat: + return "Personal Access Token"; case AzureKeyVaultConnectionMethod.OAuth: case AzureAppConfigurationConnectionMethod.OAuth: case AzureClientSecretsConnectionMethod.OAuth: diff --git a/backend/src/services/app-connection/github/github-connection-enums.ts b/backend/src/services/app-connection/github/github-connection-enums.ts index 77a4eebac..18dac354f 100644 --- a/backend/src/services/app-connection/github/github-connection-enums.ts +++ b/backend/src/services/app-connection/github/github-connection-enums.ts @@ -1,4 +1,5 @@ export enum GitHubConnectionMethod { OAuth = "oauth", - App = "github-app" + App = "github-app", + Pat = "pat" } diff --git a/backend/src/services/app-connection/github/github-connection-fns.ts b/backend/src/services/app-connection/github/github-connection-fns.ts index 5cbf5c60d..806df039f 100644 --- a/backend/src/services/app-connection/github/github-connection-fns.ts +++ b/backend/src/services/app-connection/github/github-connection-fns.ts @@ -248,10 +248,18 @@ export const makePaginatedGitHubRequest = async ( ): Promise => { const { credentials, method } = appConnection; - const token = - method === GitHubConnectionMethod.OAuth - ? credentials.accessToken - : await getGitHubAppAuthToken(appConnection, gatewayService, gatewayV2Service); + let token: string; + + switch (method) { + case GitHubConnectionMethod.OAuth: + token = credentials.accessToken; + break; + case GitHubConnectionMethod.Pat: + token = credentials.personalAccessToken; + break; + default: + token = await getGitHubAppAuthToken(appConnection, gatewayService, gatewayV2Service); + } const baseUrl = `https://${await getGitHubInstanceApiUrl(appConnection)}${path}`; const initialUrlObj = new URL(baseUrl); @@ -460,6 +468,35 @@ export const validateGitHubConnectionCredentials = async ( gatewayV2Service: Pick ) => { const { credentials, method } = config; + + // PAT validation + if (method === GitHubConnectionMethod.Pat) { + try { + const apiUrl = await getGitHubInstanceApiUrl(config); + await requestWithGitHubGateway(config, gatewayService, gatewayV2Service, { + url: `https://${apiUrl}/user`, + method: "GET", + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${credentials.personalAccessToken}`, + "X-GitHub-Api-Version": "2022-11-28" + } + }); + + return { + personalAccessToken: credentials.personalAccessToken, + instanceType: credentials.instanceType, + host: credentials.host + }; + } catch (e: unknown) { + logger.error(e, "Unable to verify GitHub PAT connection"); + + throw new BadRequestError({ + message: "Unable to validate Personal Access Token: verify token has proper permissions" + }); + } + } + const { INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET, diff --git a/backend/src/services/app-connection/github/github-connection-schemas.ts b/backend/src/services/app-connection/github/github-connection-schemas.ts index 1b8aa9c3f..20ef2c0e0 100644 --- a/backend/src/services/app-connection/github/github-connection-schemas.ts +++ b/backend/src/services/app-connection/github/github-connection-schemas.ts @@ -38,6 +38,19 @@ export const GitHubConnectionAppInputCredentialsSchema = z.union([ }) ]); +export const GitHubConnectionPatInputCredentialsSchema = z.union([ + z.object({ + personalAccessToken: z.string().trim().min(1, "Personal Access Token required"), + instanceType: z.literal("server"), + host: z.string().trim().min(1, "Host is required for server instance type") + }), + z.object({ + personalAccessToken: z.string().trim().min(1, "Personal Access Token required"), + instanceType: z.literal("cloud").optional(), + host: z.string().trim().optional() + }) +]); + export const GitHubConnectionOAuthOutputCredentialsSchema = z.union([ z.object({ accessToken: z.string(), @@ -64,6 +77,19 @@ export const GitHubConnectionAppOutputCredentialsSchema = z.union([ }) ]); +export const GitHubConnectionPatOutputCredentialsSchema = z.union([ + z.object({ + personalAccessToken: z.string(), + instanceType: z.literal("server"), + host: z.string().trim().min(1) + }), + z.object({ + personalAccessToken: z.string(), + instanceType: z.literal("cloud").optional(), + host: z.string().trim().optional() + }) +]); + export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("method", [ z.object({ method: z.literal(GitHubConnectionMethod.App).describe(AppConnections.CREATE(AppConnection.GitHub).method), @@ -76,6 +102,12 @@ export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("m credentials: GitHubConnectionOAuthInputCredentialsSchema.describe( AppConnections.CREATE(AppConnection.GitHub).credentials ) + }), + z.object({ + method: z.literal(GitHubConnectionMethod.Pat).describe(AppConnections.CREATE(AppConnection.GitHub).method), + credentials: GitHubConnectionPatInputCredentialsSchema.describe( + AppConnections.CREATE(AppConnection.GitHub).credentials + ) }) ]); @@ -88,7 +120,11 @@ export const CreateGitHubConnectionSchema = ValidateGitHubConnectionCredentialsS export const UpdateGitHubConnectionSchema = z .object({ credentials: z - .union([GitHubConnectionAppInputCredentialsSchema, GitHubConnectionOAuthInputCredentialsSchema]) + .union([ + GitHubConnectionAppInputCredentialsSchema, + GitHubConnectionOAuthInputCredentialsSchema, + GitHubConnectionPatInputCredentialsSchema + ]) .optional() .describe(AppConnections.UPDATE(AppConnection.GitHub).credentials) }) @@ -110,6 +146,10 @@ export const GitHubConnectionSchema = z.intersection( z.object({ method: z.literal(GitHubConnectionMethod.OAuth), credentials: GitHubConnectionOAuthOutputCredentialsSchema + }), + z.object({ + method: z.literal(GitHubConnectionMethod.Pat), + credentials: GitHubConnectionPatOutputCredentialsSchema }) ]) ); @@ -128,6 +168,13 @@ export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [ instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(), host: z.string().optional() }) + }), + BaseGitHubConnectionSchema.extend({ + method: z.literal(GitHubConnectionMethod.Pat), + credentials: z.object({ + instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(), + host: z.string().optional() + }) }) ]); diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts index 5b9cd78ee..a7292e366 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts @@ -2,12 +2,14 @@ import { ForbiddenError, subject } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import slugify from "@sindresorhus/slugify"; +import { Knex } from "knex"; import { ActionProjectType, TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionActions, ProjectPermissionCertificateActions, + ProjectPermissionCertificateProfileActions, ProjectPermissionPkiTemplateActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; @@ -1180,7 +1182,9 @@ export const internalCertificateAuthorityServiceFactory = ({ extendedKeyUsages, signatureAlgorithm, keyAlgorithm, - isFromProfile + isFromProfile, + internal = false, + tx }: TIssueCertFromCaDTO) => { let ca: TCertificateAuthorityWithAssociatedCa | undefined; let certificateTemplate: TCertificateTemplates | undefined; @@ -1210,19 +1214,28 @@ export const internalCertificateAuthorityServiceFactory = ({ throw new NotFoundError({ message: `Internal CA with ID '${caId}' not found` }); } - const { permission } = await permissionService.getProjectPermission({ - actor, - actorId, - projectId: ca.projectId, - actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager - }); + if (!internal) { + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: ca.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.CertificateManager + }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionCertificateActions.Create, - ProjectPermissionSub.Certificates - ); + if (isFromProfile) { + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateProfileActions.IssueCert, + ProjectPermissionSub.CertificateProfiles + ); + } else { + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateActions.Create, + ProjectPermissionSub.Certificates + ); + } + } if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" }); if (!ca.internalCa.activeCaCertId) @@ -1473,7 +1486,7 @@ export const internalCertificateAuthorityServiceFactory = ({ plainText: Buffer.from(certificateChainPem) }); - await certificateDAL.transaction(async (tx) => { + const executeIssueCertOperations = async (transaction: Knex) => { const cert = await certificateDAL.create( { caId: (ca as TCertificateAuthorities).id, @@ -1488,9 +1501,11 @@ export const internalCertificateAuthorityServiceFactory = ({ notAfter: notAfterDate, keyUsages: selectedKeyUsages, extendedKeyUsages: selectedExtendedKeyUsages, - projectId: ca!.projectId + projectId: ca!.projectId, + keyAlgorithm: effectiveKeyAlgorithm, + signatureAlgorithm: signatureAlgorithm || ca!.internalCa!.keyAlgorithm }, - tx + transaction ); await certificateBodyDAL.create( @@ -1499,7 +1514,7 @@ export const internalCertificateAuthorityServiceFactory = ({ encryptedCertificate, encryptedCertificateChain }, - tx + transaction ); await certificateSecretDAL.create( @@ -1507,7 +1522,7 @@ export const internalCertificateAuthorityServiceFactory = ({ certId: cert.id, encryptedPrivateKey }, - tx + transaction ); if (collectionId) { @@ -1516,12 +1531,18 @@ export const internalCertificateAuthorityServiceFactory = ({ pkiCollectionId: collectionId, certId: cert.id }, - tx + transaction ); } return cert; - }); + }; + + if (tx) { + await executeIssueCertOperations(tx); + } else { + await certificateDAL.transaction(executeIssueCertOperations); + } return { certificate: leafCert.toString("pem"), @@ -1593,10 +1614,17 @@ export const internalCertificateAuthorityServiceFactory = ({ actionProjectType: ActionProjectType.CertificateManager }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionCertificateActions.Create, - ProjectPermissionSub.Certificates - ); + if (dto.isFromProfile && dto.profileId) { + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateProfileActions.IssueCert, + ProjectPermissionSub.CertificateProfiles + ); + } else { + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateActions.Create, + ProjectPermissionSub.Certificates + ); + } } if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" }); @@ -1700,7 +1728,8 @@ export const internalCertificateAuthorityServiceFactory = ({ certificateAuthorityDAL, certificateAuthoritySecretDAL, projectDAL, - kmsService + kmsService, + signatureAlgorithm: alg }); const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id }); @@ -1917,7 +1946,9 @@ export const internalCertificateAuthorityServiceFactory = ({ notAfter: notAfterDate, keyUsages: selectedKeyUsages, extendedKeyUsages: selectedExtendedKeyUsages, - projectId: ca!.projectId + projectId: ca!.projectId, + keyAlgorithm: keyAlgorithm || ca!.internalCa!.keyAlgorithm, + signatureAlgorithm: signatureAlgorithm || ca!.internalCa!.keyAlgorithm }, tx ); diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-types.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-types.ts index 22cb86d28..b4b037933 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-types.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-types.ts @@ -1,3 +1,4 @@ +import { Knex } from "knex"; import { z } from "zod"; import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; @@ -139,6 +140,9 @@ export type TIssueCertFromCaDTO = { signatureAlgorithm?: CertSignatureAlgorithm; keyAlgorithm?: CertKeyAlgorithm; isFromProfile?: boolean; + profileId?: string; + internal?: boolean; + tx?: Knex; } & Omit; export type TSignCertFromCaDTO = @@ -159,6 +163,7 @@ export type TSignCertFromCaDTO = signatureAlgorithm?: string; keyAlgorithm?: string; isFromProfile?: boolean; + profileId?: string; } | ({ isInternal: false; @@ -177,6 +182,7 @@ export type TSignCertFromCaDTO = signatureAlgorithm?: string; keyAlgorithm?: string; isFromProfile?: boolean; + profileId?: string; } & Omit); export type TGetCaCertificateTemplatesDTO = { diff --git a/backend/src/services/certificate-common/certificate-constants.ts b/backend/src/services/certificate-common/certificate-constants.ts index bbd589110..4cc3afb8b 100644 --- a/backend/src/services/certificate-common/certificate-constants.ts +++ b/backend/src/services/certificate-common/certificate-constants.ts @@ -175,6 +175,26 @@ export enum CertSignatureAlgorithm { ECDSA_SHA512 = "ECDSA-SHA512" } +export enum CertificateRenewalErrorType { + TEMPLATE_VALIDATION_FAILED = "TEMPLATE_VALIDATION_FAILED", + CA_NOT_FOUND = "CA_NOT_FOUND", + CA_INACTIVE = "CA_INACTIVE", + CERTIFICATE_OUTLIVES_CA = "CERTIFICATE_OUTLIVES_CA", + TTL_TOO_SHORT = "TTL_TOO_SHORT", + NOT_ELIGIBLE = "NOT_ELIGIBLE", + VALIDITY_EXCEEDS_MAXIMUM = "VALIDITY_EXCEEDS_MAXIMUM", + NOT_ALLOWED_BY_TEMPLATE = "NOT_ALLOWED_BY_TEMPLATE", + UNKNOWN_ERROR = "UNKNOWN_ERROR" +} + +export const CERTIFICATE_RENEWAL_CONFIG = { + MIN_RENEW_BEFORE_DAYS: 1, + MAX_RENEW_BEFORE_DAYS: 30, + QUEUE_BATCH_SIZE: 100, + DAILY_CRON_SCHEDULE: "0 0 * * *", + QUEUE_START_DELAY_MS: 5000 +} as const; + export const SAN_TYPE_OPTIONS = Object.values(CertSubjectAlternativeNameType); export const KEY_USAGE_OPTIONS = Object.values(CertKeyUsageType); export const EXTENDED_KEY_USAGE_OPTIONS = Object.values(CertExtendedKeyUsageType); diff --git a/backend/src/services/certificate-common/certificate-csr-utils.ts b/backend/src/services/certificate-common/certificate-csr-utils.ts new file mode 100644 index 000000000..7578950af --- /dev/null +++ b/backend/src/services/certificate-common/certificate-csr-utils.ts @@ -0,0 +1,183 @@ +import * as x509 from "@peculiar/x509"; + +import { BadRequestError } from "@app/lib/errors"; + +import { + CertExtendedKeyUsageOIDToName, + CertKeyAlgorithm, + CertKeyUsage, + CertSignatureAlgorithm, + mapLegacyAltNameType, + TAltNameMapping, + TAltNameType +} from "../certificate/certificate-types"; +import { parseDistinguishedName } from "../certificate-authority/certificate-authority-fns"; +import { validateAndMapAltNameType } from "../certificate-authority/certificate-authority-validators"; +import { TCertificateRequest } from "../certificate-template-v2/certificate-template-v2-types"; +import { mapLegacyExtendedKeyUsageToStandard, mapLegacyKeyUsageToStandard } from "./certificate-constants"; + +/** + * Extracts certificate request data from a CSR string + * @param csr - The CSR in PEM format + * @returns TCertificateRequest object with parsed CSR data + */ +export const extractCertificateRequestFromCSR = (csr: string): TCertificateRequest => { + const csrObj = new x509.Pkcs10CertificateRequest(csr); + const subject = parseDistinguishedName(csrObj.subject); + + const certificateRequest: TCertificateRequest = { + commonName: subject.commonName, + organization: subject.organization, + organizationUnit: subject.ou, + locality: subject.locality, + state: subject.province, + country: subject.country + }; + + const csrKeyUsageExtension = csrObj.getExtension("2.5.29.15") as x509.KeyUsagesExtension; + if (csrKeyUsageExtension) { + const csrKeyUsages = Object.values(CertKeyUsage).filter( + // eslint-disable-next-line no-bitwise + (keyUsage) => (x509.KeyUsageFlags[keyUsage] & csrKeyUsageExtension.usages) !== 0 + ); + certificateRequest.keyUsages = csrKeyUsages.map(mapLegacyKeyUsageToStandard); + } + + const csrExtendedKeyUsageExtension = csrObj.getExtension("2.5.29.37") as x509.ExtendedKeyUsageExtension; + if (csrExtendedKeyUsageExtension) { + const csrExtendedKeyUsages = csrExtendedKeyUsageExtension.usages.map( + (ekuOid) => CertExtendedKeyUsageOIDToName[ekuOid as string] + ); + certificateRequest.extendedKeyUsages = csrExtendedKeyUsages.map(mapLegacyExtendedKeyUsageToStandard); + } + + const sanExtension = csrObj.extensions.find((ext) => ext.type === "2.5.29.17"); + if (sanExtension) { + const sanNames = new x509.GeneralNames(sanExtension.value); + const altNamesArray: TAltNameMapping[] = sanNames.items + .filter( + (value) => + value.type === TAltNameType.EMAIL || + value.type === TAltNameType.DNS || + value.type === TAltNameType.IP || + value.type === TAltNameType.URL + ) + .map((name): TAltNameMapping => { + const altNameType = validateAndMapAltNameType(name.value); + if (!altNameType) { + throw new BadRequestError({ message: `Invalid altName from CSR: ${name.value}` }); + } + return altNameType; + }); + + certificateRequest.subjectAlternativeNames = altNamesArray.map((altName) => ({ + type: mapLegacyAltNameType(altName.type), + value: altName.value + })); + } + + return certificateRequest; +}; + +/** + * Extracts the key algorithm and signature algorithm from a CSR + * @param csr - The CSR in PEM format + * @returns Object containing keyAlgorithm and signatureAlgorithm + */ +export const extractAlgorithmsFromCSR = (csr: string) => { + const csrObj = new x509.Pkcs10CertificateRequest(csr); + + // Extract key algorithm from public key + const { publicKey } = csrObj; + let keyAlgorithm: CertKeyAlgorithm; + + if (publicKey.algorithm.name === "RSASSA-PKCS1-v1_5") { + const rsaPublicKey = publicKey as unknown as { algorithm: { modulusLength: number } }; + const keySize = rsaPublicKey.algorithm.modulusLength; + switch (keySize) { + case 2048: + keyAlgorithm = CertKeyAlgorithm.RSA_2048; + break; + case 3072: + keyAlgorithm = CertKeyAlgorithm.RSA_3072; + break; + case 4096: + keyAlgorithm = CertKeyAlgorithm.RSA_4096; + break; + default: + throw new BadRequestError({ + message: `Unsupported RSA key size in CSR: ${keySize}. Supported: 2048, 3072, 4096` + }); + } + } else if (publicKey.algorithm.name === "ECDSA") { + const ecPublicKey = publicKey as unknown as { algorithm: { namedCurve: string } }; + const { namedCurve } = ecPublicKey.algorithm; + switch (namedCurve) { + case "P-256": + keyAlgorithm = CertKeyAlgorithm.ECDSA_P256; + break; + case "P-384": + keyAlgorithm = CertKeyAlgorithm.ECDSA_P384; + break; + case "P-521": + keyAlgorithm = CertKeyAlgorithm.ECDSA_P521; + break; + default: + throw new BadRequestError({ + message: `Unsupported ECDSA curve in CSR: ${namedCurve}. Supported: P-256, P-384, P-521` + }); + } + } else { + throw new BadRequestError({ + message: `Unsupported key algorithm in CSR: ${publicKey.algorithm.name}. Supported: RSASSA-PKCS1-v1_5, ECDSA` + }); + } + + const signatureAlgorithm = csrObj.signatureAlgorithm.name; + const hashName = (csrObj.signatureAlgorithm as unknown as { hash?: { name: string } }).hash?.name; + + let normalizedSignatureAlg: CertSignatureAlgorithm; + + if (signatureAlgorithm === "RSASSA-PKCS1-v1_5") { + switch (hashName) { + case "SHA-256": + normalizedSignatureAlg = CertSignatureAlgorithm.RSA_SHA256; + break; + case "SHA-384": + normalizedSignatureAlg = CertSignatureAlgorithm.RSA_SHA384; + break; + case "SHA-512": + normalizedSignatureAlg = CertSignatureAlgorithm.RSA_SHA512; + break; + default: + throw new BadRequestError({ + message: `Unsupported RSA hash algorithm in CSR: ${hashName}. Supported: SHA-256, SHA-384, SHA-512` + }); + } + } else if (signatureAlgorithm === "ECDSA") { + switch (hashName) { + case "SHA-256": + normalizedSignatureAlg = CertSignatureAlgorithm.ECDSA_SHA256; + break; + case "SHA-384": + normalizedSignatureAlg = CertSignatureAlgorithm.ECDSA_SHA384; + break; + case "SHA-512": + normalizedSignatureAlg = CertSignatureAlgorithm.ECDSA_SHA512; + break; + default: + throw new BadRequestError({ + message: `Unsupported ECDSA hash algorithm in CSR: ${hashName}. Supported: SHA-256, SHA-384, SHA-512` + }); + } + } else { + throw new BadRequestError({ + message: `Unsupported signature algorithm in CSR: ${signatureAlgorithm}. Supported: RSASSA-PKCS1-v1_5, ECDSA` + }); + } + + return { + keyAlgorithm, + signatureAlgorithm: normalizedSignatureAlg + }; +}; diff --git a/backend/src/services/certificate-est-v3/certificate-est-v3-service.ts b/backend/src/services/certificate-est-v3/certificate-est-v3-service.ts index f6dbfba52..0d8ef30d0 100644 --- a/backend/src/services/certificate-est-v3/certificate-est-v3-service.ts +++ b/backend/src/services/certificate-est-v3/certificate-est-v3-service.ts @@ -3,31 +3,15 @@ import * as x509 from "@peculiar/x509"; import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate"; import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { isCertChainValid } from "@app/services/certificate/certificate-fns"; -import { - CertExtendedKeyUsageOIDToName, - CertKeyUsage, - mapLegacyAltNameType, - TAltNameMapping, - TAltNameType -} from "@app/services/certificate/certificate-types"; import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; -import { - getCaCertChain, - getCaCertChains, - parseDistinguishedName -} from "@app/services/certificate-authority/certificate-authority-fns"; -import { validateAndMapAltNameType } from "@app/services/certificate-authority/certificate-authority-validators"; +import { getCaCertChain, getCaCertChains } from "@app/services/certificate-authority/certificate-authority-fns"; import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service"; -import { - mapLegacyExtendedKeyUsageToStandard, - mapLegacyKeyUsageToStandard -} from "@app/services/certificate-common/certificate-constants"; +import { extractCertificateRequestFromCSR } from "@app/services/certificate-common/certificate-csr-utils"; import { mapEnumsForValidation } from "@app/services/certificate-common/certificate-utils"; import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types"; import { TCertificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; -import { TCertificateRequest } from "@app/services/certificate-template-v2/certificate-template-v2-types"; import { TEstEnrollmentConfigDALFactory } from "@app/services/enrollment-config/est-enrollment-config-dal"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TProjectDALFactory } from "@app/services/project/project-dal"; @@ -61,63 +45,6 @@ export const certificateEstV3ServiceFactory = ({ certificateProfileDAL, estEnrollmentConfigDAL }: TCertificateEstV3ServiceFactoryDep) => { - const extractCertificateRequestFromCSR = (csr: string): TCertificateRequest => { - const csrObj = new x509.Pkcs10CertificateRequest(csr); - const subject = parseDistinguishedName(csrObj.subject); - - const certificateRequest: TCertificateRequest = { - commonName: subject.commonName, - organization: subject.organization, - organizationUnit: subject.ou, - locality: subject.locality, - state: subject.province, - country: subject.country - }; - - const csrKeyUsageExtension = csrObj.getExtension("2.5.29.15") as x509.KeyUsagesExtension; - if (csrKeyUsageExtension) { - const csrKeyUsages = Object.values(CertKeyUsage).filter( - // eslint-disable-next-line no-bitwise - (keyUsage) => (x509.KeyUsageFlags[keyUsage] & csrKeyUsageExtension.usages) !== 0 - ); - certificateRequest.keyUsages = csrKeyUsages.map(mapLegacyKeyUsageToStandard); - } - - const csrExtendedKeyUsageExtension = csrObj.getExtension("2.5.29.37") as x509.ExtendedKeyUsageExtension; - if (csrExtendedKeyUsageExtension) { - const csrExtendedKeyUsages = csrExtendedKeyUsageExtension.usages.map( - (ekuOid) => CertExtendedKeyUsageOIDToName[ekuOid as string] - ); - certificateRequest.extendedKeyUsages = csrExtendedKeyUsages.map(mapLegacyExtendedKeyUsageToStandard); - } - - const sanExtension = csrObj.extensions.find((ext) => ext.type === "2.5.29.17"); - if (sanExtension) { - const sanNames = new x509.GeneralNames(sanExtension.value); - const altNamesArray: TAltNameMapping[] = sanNames.items - .filter( - (value) => - value.type === TAltNameType.EMAIL || - value.type === TAltNameType.DNS || - value.type === TAltNameType.IP || - value.type === TAltNameType.URL - ) - .map((name): TAltNameMapping => { - const altNameType = validateAndMapAltNameType(name.value); - if (!altNameType) { - throw new BadRequestError({ message: `Invalid altName from CSR: ${name.value}` }); - } - return altNameType; - }); - - certificateRequest.subjectAlternativeNames = altNamesArray.map((altName) => ({ - type: mapLegacyAltNameType(altName.type), - value: altName.value - })); - } - - return certificateRequest; - }; const simpleEnrollByProfile = async ({ csr, profileId, diff --git a/backend/src/services/certificate-profile/certificate-profile-dal.ts b/backend/src/services/certificate-profile/certificate-profile-dal.ts index 20cb9f3bc..1ffa3e295 100644 --- a/backend/src/services/certificate-profile/certificate-profile-dal.ts +++ b/backend/src/services/certificate-profile/certificate-profile-dal.ts @@ -109,7 +109,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => { db.ref("encryptedCaChain").withSchema(TableName.PkiEstEnrollmentConfig).as("estConfigEncryptedCaChain"), db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigId"), db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigAutoRenew"), - db.ref("autoRenewDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigAutoRenewDays") + db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigRenewBeforeDays") ) .where(`${TableName.PkiCertificateProfile}.id`, id) .first(); @@ -132,7 +132,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => { ? ({ id: result.apiConfigId, autoRenew: !!result.apiConfigAutoRenew, - autoRenewDays: result.apiConfigAutoRenewDays || undefined + renewBeforeDays: result.apiConfigRenewBeforeDays || undefined } as TCertificateProfileWithConfigs["apiConfig"]) : undefined; @@ -264,7 +264,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => { db.ref("encryptedCaChain").withSchema(TableName.PkiEstEnrollmentConfig).as("estEncryptedCaChain"), db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiId"), db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenew"), - db.ref("autoRenewDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenewDays") + db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays") ); if (includeMetrics) { @@ -290,7 +290,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => { db.ref("encryptedCaChain").withSchema(TableName.PkiEstEnrollmentConfig).as("estEncryptedCaChain"), db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiId"), db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenew"), - db.ref("autoRenewDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenewDays"), + db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays"), db.raw("COUNT(certificates.id) as total_certificates"), db.raw( 'COUNT(CASE WHEN certificates."revokedAt" IS NULL AND certificates."notAfter" > ? THEN 1 END) as active_certificates', @@ -333,7 +333,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => { ? { id: result.apiId as string, autoRenew: !!result.apiAutoRenew, - autoRenewDays: (result.apiAutoRenewDays as number) || undefined + renewBeforeDays: (result.apiRenewBeforeDays as number) || undefined } : undefined; diff --git a/backend/src/services/certificate-profile/certificate-profile-schemas.ts b/backend/src/services/certificate-profile/certificate-profile-schemas.ts index 7b3e2cc57..a2c391c2a 100644 --- a/backend/src/services/certificate-profile/certificate-profile-schemas.ts +++ b/backend/src/services/certificate-profile/certificate-profile-schemas.ts @@ -25,7 +25,7 @@ export const createCertificateProfileSchema = z apiConfig: z .object({ autoRenew: z.boolean().default(false), - autoRenewDays: z.number().min(1).max(365).optional() + renewBeforeDays: z.number().min(1).max(30).optional() }) .optional() }) @@ -75,7 +75,7 @@ export const updateCertificateProfileSchema = z apiConfig: z .object({ autoRenew: z.boolean().default(false), - autoRenewDays: z.number().min(1).max(365).optional() + renewBeforeDays: z.number().min(1).max(30).optional() }) .optional() }) diff --git a/backend/src/services/certificate-profile/certificate-profile-service.test.ts b/backend/src/services/certificate-profile/certificate-profile-service.test.ts index dd2d7d2d6..26b1e976a 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.test.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.test.ts @@ -110,7 +110,7 @@ describe("CertificateProfileService", () => { apiConfig: { id: "api-config-123", autoRenew: true, - autoRenewDays: 30 + renewBeforeDays: 30 } }; @@ -202,7 +202,7 @@ describe("CertificateProfileService", () => { certificateTemplateId: "template-123", apiConfig: { autoRenew: true, - autoRenewDays: 30 + renewBeforeDays: 30 } }; @@ -323,7 +323,7 @@ describe("CertificateProfileService", () => { certificateTemplateId: "template-123", apiConfig: { autoRenew: true, - autoRenewDays: 30 + renewBeforeDays: 30 } }; @@ -761,7 +761,7 @@ describe("CertificateProfileService", () => { certificateTemplateId: "template-123", apiConfig: { autoRenew: true, - autoRenewDays: 30 + renewBeforeDays: 30 } }; @@ -786,7 +786,7 @@ describe("CertificateProfileService", () => { certificateTemplateId: "template-123", apiConfig: { autoRenew: true, - autoRenewDays: 7 + renewBeforeDays: 7 } }; @@ -808,7 +808,7 @@ describe("CertificateProfileService", () => { expect(mockApiEnrollmentConfigDAL.create).toHaveBeenCalledWith( { autoRenew: true, - autoRenewDays: 7 + renewBeforeDays: 7 }, undefined ); diff --git a/backend/src/services/certificate-profile/certificate-profile-service.ts b/backend/src/services/certificate-profile/certificate-profile-service.ts index c43dee889..7b48af8f1 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.ts @@ -225,7 +225,7 @@ export const certificateProfileServiceFactory = ({ const apiConfig = await apiEnrollmentConfigDAL.create( { autoRenew: data.apiConfig.autoRenew, - autoRenewDays: data.apiConfig.autoRenewDays + renewBeforeDays: data.apiConfig.renewBeforeDays }, tx ); @@ -343,7 +343,7 @@ export const certificateProfileServiceFactory = ({ existingProfile.apiConfigId, { autoRenew: apiConfig.autoRenew, - autoRenewDays: apiConfig.autoRenewDays + renewBeforeDays: apiConfig.renewBeforeDays }, tx ); diff --git a/backend/src/services/certificate-profile/certificate-profile-types.ts b/backend/src/services/certificate-profile/certificate-profile-types.ts index a6d53a0f3..1c22a5e75 100644 --- a/backend/src/services/certificate-profile/certificate-profile-types.ts +++ b/backend/src/services/certificate-profile/certificate-profile-types.ts @@ -26,7 +26,7 @@ export type TCertificateProfileUpdate = Omit => { const template = await certificateTemplateV2DAL.findById(templateId); if (!template) { throw new NotFoundError({ message: "Certificate template not found" }); } - const { permission } = await permissionService.getProjectPermission({ - actor, - actorId, - projectId: template.projectId, - actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager - }); + if (!internal) { + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: template.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.CertificateManager + }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionPkiTemplateActions.Read, - ProjectPermissionSub.CertificateTemplates - ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionPkiTemplateActions.Read, + ProjectPermissionSub.CertificateTemplates + ); + } return template; }; diff --git a/backend/src/services/certificate-v3/certificate-v3-queue.ts b/backend/src/services/certificate-v3/certificate-v3-queue.ts new file mode 100644 index 000000000..db7349cd5 --- /dev/null +++ b/backend/src/services/certificate-v3/certificate-v3-queue.ts @@ -0,0 +1,163 @@ +/* eslint-disable no-await-in-loop */ +import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; +import { getConfig } from "@app/lib/config/env"; +import { logger } from "@app/lib/logger"; +import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; + +import { ActorType } from "../auth/auth-type"; +import { TCertificateDALFactory } from "../certificate/certificate-dal"; +import { CERTIFICATE_RENEWAL_CONFIG } from "../certificate-common/certificate-constants"; +import { TCertificateV3ServiceFactory } from "./certificate-v3-service"; + +type TCertificateV3QueueServiceFactoryDep = { + queueService: TQueueServiceFactory; + certificateDAL: Pick; + certificateV3Service: TCertificateV3ServiceFactory; + auditLogService: Pick; +}; + +export const certificateV3QueueServiceFactory = ({ + queueService, + certificateDAL, + certificateV3Service, + auditLogService +}: TCertificateV3QueueServiceFactoryDep) => { + const appCfg = getConfig(); + + const init = async () => { + if (appCfg.isSecondaryInstance) { + return; + } + + await queueService.stopRepeatableJob( + QueueName.CertificateV3AutoRenewal, + QueueJobs.CertificateV3DailyAutoRenewal, + { pattern: CERTIFICATE_RENEWAL_CONFIG.DAILY_CRON_SCHEDULE, utc: true }, + QueueName.CertificateV3AutoRenewal + ); + + await queueService.startPg( + QueueJobs.CertificateV3DailyAutoRenewal, + async () => { + try { + logger.info(`${QueueJobs.CertificateV3DailyAutoRenewal}: queue task started`); + + const { QUEUE_BATCH_SIZE } = CERTIFICATE_RENEWAL_CONFIG; + let offset = 0; + let hasMore = true; + let totalCertificatesFound = 0; + let totalCertificatesRenewed = 0; + + while (hasMore) { + const certificates = await certificateDAL.findCertificatesEligibleForRenewal({ + limit: QUEUE_BATCH_SIZE, + offset + }); + + if (certificates.length === 0) { + hasMore = false; + break; + } + + totalCertificatesFound += certificates.length; + logger.info( + `${QueueJobs.CertificateV3DailyAutoRenewal}: found ${certificates.length} certificates eligible for renewal (batch ${Math.floor(offset / QUEUE_BATCH_SIZE) + 1}, total found so far: ${totalCertificatesFound})` + ); + + for (const certificate of certificates) { + try { + if (certificate.renewBeforeDays) { + const { MIN_RENEW_BEFORE_DAYS, MAX_RENEW_BEFORE_DAYS } = CERTIFICATE_RENEWAL_CONFIG; + if ( + certificate.renewBeforeDays < MIN_RENEW_BEFORE_DAYS || + certificate.renewBeforeDays > MAX_RENEW_BEFORE_DAYS + ) { + // eslint-disable-next-line no-continue + continue; + } + } + + await certificateV3Service.renewCertificate({ + actor: ActorType.PLATFORM, + actorId: "", + actorAuthMethod: null, + actorOrgId: "", + certificateId: certificate.id, + internal: true + }); + + totalCertificatesRenewed += 1; + + await auditLogService.createAuditLog({ + projectId: certificate.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.AUTOMATED_RENEW_CERTIFICATE, + metadata: { + certificateId: certificate.id, + commonName: certificate.commonName || "", + profileId: certificate.profileId!, + renewBeforeDays: certificate.renewBeforeDays?.toString() || "", + profileName: certificate.profileName || "" + } + } + }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(error, `Failed to renew certificate ${certificate.id}: ${errorMessage}`); + await auditLogService.createAuditLog({ + projectId: certificate.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.AUTOMATED_RENEW_CERTIFICATE_FAILED, + metadata: { + certificateId: certificate.id, + commonName: certificate.commonName || "", + profileId: certificate.profileId || "", + renewBeforeDays: certificate.renewBeforeDays?.toString() || "", + profileName: certificate.profileName || "", + error: errorMessage + } + } + }); + } + } + + offset += QUEUE_BATCH_SIZE; + } + + logger.info( + `${QueueJobs.CertificateV3DailyAutoRenewal}: queue task completed. Renewed ${totalCertificatesRenewed} certificates out of ${totalCertificatesFound}` + ); + } catch (error) { + logger.error(error, `${QueueJobs.CertificateV3DailyAutoRenewal}: certificate renewal failed`); + throw error; + } + }, + { + batchSize: 1, + workerCount: 1, + pollingIntervalSeconds: 60 + } + ); + + await queueService.schedulePg( + QueueJobs.CertificateV3DailyAutoRenewal, + CERTIFICATE_RENEWAL_CONFIG.DAILY_CRON_SCHEDULE, + undefined, + { tz: "UTC" } + ); + }; + + return { + init + }; +}; + +export type TCertificateV3QueueServiceFactory = ReturnType; diff --git a/backend/src/services/certificate-v3/certificate-v3-service.test.ts b/backend/src/services/certificate-v3/certificate-v3-service.test.ts index 95f9a5077..0c70571dd 100644 --- a/backend/src/services/certificate-v3/certificate-v3-service.test.ts +++ b/backend/src/services/certificate-v3/certificate-v3-service.test.ts @@ -7,10 +7,12 @@ import { ForbiddenError } from "@casl/ability"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; -import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; +import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; -import { ACMESANType, CertificateOrderStatus } from "@app/services/certificate/certificate-types"; +import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; +import { ACMESANType, CertificateOrderStatus, CertStatus } from "@app/services/certificate/certificate-types"; import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; +import { CaStatus } from "@app/services/certificate-authority/certificate-authority-enums"; import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service"; import { CertExtendedKeyUsageType, @@ -23,14 +25,32 @@ import { EnrollmentType } from "@app/services/certificate-profile/certificate-pr import { TCertificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { ActorType, AuthMethod } from "../auth/auth-type"; +import { + extractAlgorithmsFromCSR, + extractCertificateRequestFromCSR +} from "../certificate-common/certificate-csr-utils"; import { certificateV3ServiceFactory, TCertificateV3ServiceFactory } from "./certificate-v3-service"; +vi.mock("../certificate-common/certificate-csr-utils", () => ({ + extractCertificateRequestFromCSR: vi.fn(), + extractAlgorithmsFromCSR: vi.fn() +})); + describe("CertificateV3Service", () => { let service: TCertificateV3ServiceFactory; - const mockCertificateDAL: Pick = { + const mockCertificateDAL: Pick = { findOne: vi.fn(), - updateById: vi.fn() + findById: vi.fn(), + updateById: vi.fn(), + transaction: vi.fn().mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + return callback(mockTx); + }) + }; + + const mockCertificateSecretDAL: Pick = { + findOne: vi.fn() }; const mockCertificateAuthorityDAL: Pick = { @@ -76,7 +96,7 @@ describe("CertificateV3Service", () => { beforeEach(() => { // Reset all mocks before each test - vi.clearAllMocks(); + vi.resetAllMocks(); // Mock ForbiddenError.from static method vi.spyOn(ForbiddenError, "from").mockReturnValue({ @@ -95,8 +115,20 @@ describe("CertificateV3Service", () => { } }); + vi.mocked(extractCertificateRequestFromCSR).mockReturnValue({ + commonName: "test.example.com", + keyUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + extendedKeyUsages: [CertExtendedKeyUsageType.SERVER_AUTH] + }); + + vi.mocked(extractAlgorithmsFromCSR).mockReturnValue({ + keyAlgorithm: "RSA_2048" as any, + signatureAlgorithm: "RSA-SHA256" as any + }); + service = certificateV3ServiceFactory({ certificateDAL: mockCertificateDAL, + certificateSecretDAL: mockCertificateSecretDAL, certificateAuthorityDAL: mockCertificateAuthorityDAL, certificateProfileDAL: mockCertificateProfileDAL, certificateTemplateV2Service: mockCertificateTemplateV2Service, @@ -641,6 +673,11 @@ describe("CertificateV3Service", () => { vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); + vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ + isValid: true, + errors: [], + warnings: [] + }); vi.mocked(mockInternalCaService.signCertFromCa).mockResolvedValue(mockSignResult as any); vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCertRecord); @@ -1460,4 +1497,714 @@ describe("CertificateV3Service", () => { ).resolves.toBeDefined(); }); }); + + describe("renewCertificate", () => { + const mockOriginalCert = { + id: "cert-123", + status: CertStatus.ACTIVE, + serialNumber: "123456", + friendlyName: "Test Certificate", + commonName: "test.example.com", + notBefore: new Date("2024-01-01"), + notAfter: new Date("2024-02-01"), // 31 days + revokedAt: null, + renewedByCertificateId: null, + profileId: "profile-123", + renewBeforeDays: 7, + caId: "ca-123", + pkiSubscriberId: null, + keyUsages: ["digital_signature", "key_agreement"], + extendedKeyUsages: ["server_auth"], + altNames: "test.example.com,api.example.com", + projectId: "project-123", + createdAt: new Date(), + updatedAt: new Date(), + certificateTemplateId: "template-123", + revocationReason: null, + caCertId: null, + renewedFromCertificateId: null, + renewalError: null, + keyAlgorithm: "RSA_2048", + signatureAlgorithm: "RSA-SHA256" + }; + + const mockProfile = { + id: "profile-123", + projectId: "project-123", + enrollmentType: EnrollmentType.API, + caId: "ca-123", + certificateTemplateId: "template-123", + apiConfig: { + id: "api-config-123", + autoRenew: true, + renewBeforeDays: 14 + }, + createdAt: new Date(), + updatedAt: new Date(), + slug: "test-profile", + description: "Test profile" + }; + + const mockCA = { + id: "ca-123", + projectId: "project-123", + status: CaStatus.ACTIVE, + createdAt: new Date(), + updatedAt: new Date(), + enableDirectIssuance: true, + name: "Test CA", + requireTemplateForIssuance: false, + externalCa: undefined, + parentCaId: null, + type: "ROOT", + friendlyName: "Test CA", + organization: "Test Org", + ou: "Test OU", + country: "US", + province: "CA", + locality: "SF", + commonName: "Test CA", + keyAlgorithm: "RSA_2048", + notAfter: "2025-01-01T00:00:00Z", + notBefore: "2024-01-01T00:00:00Z", + maxPathLength: -1, + activeCaCertId: "cert-123", + dn: "CN=Test CA,O=Test Org,OU=Test OU,C=US", + serialNumber: "123456789", + internalCa: { + id: "internal-ca-123", + parentCaId: null, + type: "ROOT", + friendlyName: "Test CA", + organization: "Test Org", + ou: "Test OU", + country: "US", + province: "CA", + locality: "SF", + commonName: "Test CA", + keyAlgorithm: "RSA_2048", + notAfter: "2025-01-01T00:00:00Z", + notBefore: "2024-01-01T00:00:00Z", + maxPathLength: -1, + activeCaCertId: "cert-123", + dn: "CN=Test CA,O=Test Org,OU=Test OU,C=US", + serialNumber: "123456789" + } + }; + + const mockTemplate = { + id: "template-123", + projectId: "project-123", + name: "Test Template", + createdAt: new Date(), + updatedAt: new Date(), + algorithms: { + signature: ["SHA256-RSA", "SHA384-RSA"], + keyType: ["RSA_2048", "RSA_4096"] + } + }; + + beforeEach(() => { + // Mock current date to be within renewal window + vi.useFakeTimers(); + vi.setSystemTime(new Date("2024-01-26")); // 6 days before cert expires, within renewal window + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should successfully renew eligible certificate", async () => { + // Mock the initial findById call + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); + vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); + vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); + vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ + isValid: true, + errors: [], + warnings: [] + }); + vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue({ + certificate: "renewed-cert", + certificateChain: "renewed-chain", + issuingCaCertificate: "issuing-ca", + privateKey: "private-key", + serialNumber: "789012", + ca: mockCA + }); + + const newCert = { ...mockOriginalCert, id: "cert-456", serialNumber: "789012" }; + vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(newCert); + vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(newCert); + + // Mock the transaction to return the expected structure + vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + const result = await callback(mockTx); + return result; + }); + + const result = await service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }); + + expect(result).toHaveProperty("certificate", "renewed-cert"); + expect(result).toHaveProperty("certificateId", "cert-456"); + expect(mockCertificateDAL.updateById).toHaveBeenCalledWith( + "cert-456", + { + profileId: "profile-123", + renewBeforeDays: 14, + renewedFromCertificateId: "cert-123" + }, + {} + ); + expect(mockCertificateDAL.updateById).toHaveBeenCalledWith( + "cert-123", + { + renewedByCertificateId: "cert-456", + renewalError: null + }, + {} + ); + }); + + it("should validate certificate against current template during renewal", async () => { + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); + vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); + vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); + vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ + isValid: false, + errors: ["Subject alternative name not allowed"], + warnings: [] + }); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + + // Mock updateById to handle the renewal error logging + vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockOriginalCert); + + // Set up transaction mock to properly handle errors + vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + return callback(mockTx); + }); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow(BadRequestError); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow("Certificate renewal failed. Errors: Subject alternative name not allowed"); + + // Should store template validation error + expect(mockCertificateDAL.updateById).toHaveBeenCalledWith("cert-123", { + renewalError: "Template validation failed: Subject alternative name not allowed" + }); + }); + + it("should reject renewal if certificate is not from a profile", async () => { + const certWithoutProfile = { ...mockOriginalCert, profileId: null }; + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(certWithoutProfile); + + // Set up transaction mock to properly handle errors + vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + return callback(mockTx); + }); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow(ForbiddenRequestError); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow("Only certificates issued from a profile can be renewed"); + }); + + it("should reject renewal if certificate was issued from CSR (external private key)", async () => { + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue(null as any); + + vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + return callback(mockTx); + }); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow(ForbiddenRequestError); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow("certificates issued from CSR (external private key) cannot be renewed"); + }); + + it("should reject renewal if certificate is already renewed", async () => { + const alreadyRenewedCert = { ...mockOriginalCert, renewedByCertificateId: "cert-456" }; + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(alreadyRenewedCert); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); + vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + + // Mock updateById to handle the renewal error logging + vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(alreadyRenewedCert); + + // Set up transaction mock to properly handle errors + vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + return callback(mockTx); + }); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow(BadRequestError); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow("Certificate has already been renewed"); + }); + + it("should reject renewal if certificate is expired", async () => { + const expiredCert = { + ...mockOriginalCert, + notAfter: new Date("2024-01-20") // Expired 6 days ago + }; + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(expiredCert); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); + vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + + // Mock updateById to handle the renewal error logging + vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(expiredCert); + + // Set up transaction mock to properly handle errors + vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + return callback(mockTx); + }); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow(BadRequestError); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow("Certificate is already expired"); + }); + + it("should reject renewal if certificate is revoked", async () => { + const revokedCert = { + ...mockOriginalCert, + revokedAt: new Date("2024-01-15") + }; + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(revokedCert); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); + vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + + // Mock updateById to handle the renewal error logging + vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(revokedCert); + + // Set up transaction mock to properly handle errors + vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + return callback(mockTx); + }); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow(BadRequestError); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow("Certificate is revoked and cannot be renewed"); + }); + + it("should reject renewal if CA is inactive", async () => { + const inactiveCA = { ...mockCA, status: CaStatus.DISABLED }; + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); + vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(inactiveCA); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + + // Mock updateById to handle the renewal error logging + vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockOriginalCert); + + // Set up transaction mock to properly handle errors + vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + return callback(mockTx); + }); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow(BadRequestError); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow("Certificate is not eligible for renewal: Certificate Authority is disabled, must be active"); + }); + + it("should reject renewal if new certificate would outlive CA", async () => { + const shortLivedCA = { + ...mockCA, + internalCa: { + ...mockCA.internalCa, + notAfter: "2024-01-28T00:00:00Z" + } + }; + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); + vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(shortLivedCA); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + + // Mock updateById to handle the renewal error logging + vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockOriginalCert); + + // Set up transaction mock to properly handle errors + vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + return callback(mockTx); + }); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow(BadRequestError); + + await expect( + service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }) + ).rejects.toThrow(/New certificate would expire \(.+\) after its issuing CA \(.+\)/); + }); + + it("should allow manual renewal outside window (manual renewal always bypasses window)", async () => { + vi.setSystemTime(new Date("2024-01-15")); // 17 days before expiry, outside 7-day window + + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); + vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); + vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ + isValid: true, + errors: [], + warnings: [] + }); + vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue({ + certificate: "renewed-cert", + certificateChain: "renewed-chain", + issuingCaCertificate: "issuing-ca", + privateKey: "private-key", + serialNumber: "789012", + ca: mockCA + }); + + const newCert = { ...mockOriginalCert, id: "cert-456", serialNumber: "789012" }; + vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(newCert); + vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(newCert); + + // Set up transaction mock to properly handle the renewal process + vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { + const mockTx = {}; + return callback(mockTx); + }); + + const result = await service.renewCertificate({ + certificateId: "cert-123", + ...mockActor + }); + + expect(result).toHaveProperty("certificate", "renewed-cert"); + }); + }); + + describe("updateRenewalConfig", () => { + it("should update renewal configuration successfully", async () => { + const mockCert = { + id: "cert-123", + profileId: "profile-123", + renewedByCertificateId: null, + notBefore: new Date("2026-01-01"), + notAfter: new Date("2026-02-01"), + projectId: "project-123", + status: CertStatus.ACTIVE, + revokedAt: null, + commonName: "" + }; + + const mockProfile = { + id: "profile-123", + enrollmentType: EnrollmentType.API, + projectId: "project-123" + }; + + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile as any); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCert as any); + + const result = await service.updateRenewalConfig({ + actor: ActorType.USER, + actorId: "user-123", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "org-123", + certificateId: "cert-123", + renewBeforeDays: 7 + }); + + expect(result).toEqual({ + projectId: "project-123", + renewBeforeDays: 7, + commonName: "" + }); + + expect(mockCertificateDAL.updateById).toHaveBeenCalledWith("cert-123", { + renewBeforeDays: 7 + }); + }); + + it("should reject update if certificate is not from profile", async () => { + const mockCert = { + id: "cert-123", + profileId: null, + renewedByCertificateId: null, + projectId: "project-123" + }; + + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); + + await expect( + service.updateRenewalConfig({ + actor: ActorType.USER, + actorId: "user-123", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "org-123", + certificateId: "cert-123", + renewBeforeDays: 7 + }) + ).rejects.toThrow(BadRequestError); + + await expect( + service.updateRenewalConfig({ + actor: ActorType.USER, + actorId: "user-123", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "org-123", + certificateId: "cert-123", + renewBeforeDays: 7 + }) + ).rejects.toThrow("Certificate is not eligible for auto-renewal: certificate was not issued from a profile"); + }); + + it("should reject update if certificate is already renewed", async () => { + const mockCert = { + id: "cert-123", + profileId: "profile-123", + renewedByCertificateId: "cert-456", + projectId: "project-123", + status: CertStatus.ACTIVE, + revokedAt: null, + notBefore: new Date("2026-01-01"), + notAfter: new Date("2026-02-01") + }; + + const mockProfile = { + id: "profile-123", + enrollmentType: EnrollmentType.API, + projectId: "project-123" + }; + + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile as any); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + + await expect( + service.updateRenewalConfig({ + actor: ActorType.USER, + actorId: "user-123", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "org-123", + certificateId: "cert-123", + renewBeforeDays: 7 + }) + ).rejects.toThrow(BadRequestError); + + await expect( + service.updateRenewalConfig({ + actor: ActorType.USER, + actorId: "user-123", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "org-123", + certificateId: "cert-123", + renewBeforeDays: 7 + }) + ).rejects.toThrow("Certificate is not eligible for auto-renewal: certificate has already been renewed"); + }); + + it("should reject update if renewBeforeDays >= certificate TTL", async () => { + const mockCert = { + id: "cert-123", + profileId: "profile-123", + renewedByCertificateId: null, + notBefore: new Date("2026-01-01"), + notAfter: new Date("2026-01-08"), + projectId: "project-123", + status: CertStatus.ACTIVE, + revokedAt: null + }; + + const mockProfile = { + id: "profile-123", + enrollmentType: EnrollmentType.API, + projectId: "project-123" + }; + + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile as any); + vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); + + await expect( + service.updateRenewalConfig({ + actor: ActorType.USER, + actorId: "user-123", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "org-123", + certificateId: "cert-123", + renewBeforeDays: 8 // Greater than 7-day TTL + }) + ).rejects.toThrow(BadRequestError); + + await expect( + service.updateRenewalConfig({ + actor: ActorType.USER, + actorId: "user-123", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "org-123", + certificateId: "cert-123", + renewBeforeDays: 8 + }) + ).rejects.toThrow("Invalid renewal configuration: renewal threshold exceeds certificate validity period"); + }); + }); + + describe("disableRenewalConfig", () => { + it("should disable renewal configuration successfully", async () => { + const mockCert = { + id: "cert-123", + profileId: "profile-123", + projectId: "project-123", + commonName: "" + }; + + const mockProfile = { + id: "profile-123", + enrollmentType: EnrollmentType.API, + projectId: "project-123" + }; + + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); + vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile as any); + vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCert as any); + + const result = await service.disableRenewalConfig({ + actor: ActorType.USER, + actorId: "user-123", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "org-123", + certificateId: "cert-123" + }); + + expect(result).toEqual({ + projectId: "project-123", + commonName: "" + }); + + expect(mockCertificateDAL.updateById).toHaveBeenCalledWith("cert-123", { + renewBeforeDays: null + }); + }); + + it("should reject disable if certificate is not from profile", async () => { + const mockCert = { + id: "cert-123", + profileId: null, + projectId: "project-123" + }; + + vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); + + await expect( + service.disableRenewalConfig({ + actor: ActorType.USER, + actorId: "user-123", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "org-123", + certificateId: "cert-123" + }) + ).rejects.toThrow(BadRequestError); + + await expect( + service.disableRenewalConfig({ + actor: ActorType.USER, + actorId: "user-123", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "org-123", + certificateId: "cert-123" + }) + ).rejects.toThrow("Certificate is not eligible for auto-renewal: certificate was not issued from a profile"); + }); + }); }); diff --git a/backend/src/services/certificate-v3/certificate-v3-service.ts b/backend/src/services/certificate-v3/certificate-v3-service.ts index 1c11b0a00..0a721b2db 100644 --- a/backend/src/services/certificate-v3/certificate-v3-service.ts +++ b/backend/src/services/certificate-v3/certificate-v3-service.ts @@ -1,36 +1,49 @@ import { ForbiddenError } from "@casl/ability"; import { randomUUID } from "crypto"; +import RE2 from "re2"; import { ActionProjectType } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { + ProjectPermissionCertificateActions, ProjectPermissionCertificateProfileActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; import { + CertExtendedKeyUsage, CertificateOrderStatus, CertKeyAlgorithm, - CertSignatureAlgorithm + CertKeyType, + CertKeyUsage, + CertSignatureAlgorithm, + CertStatus } from "@app/services/certificate/certificate-types"; import { TCertificateAuthorityDALFactory, TCertificateAuthorityWithAssociatedCa } from "@app/services/certificate-authority/certificate-authority-dal"; -import { CaType } from "@app/services/certificate-authority/certificate-authority-enums"; +import { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-enums"; import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service"; import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types"; import { TCertificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { CertSubjectAlternativeNameType } from "../certificate-common/certificate-constants"; +import { + extractAlgorithmsFromCSR, + extractCertificateRequestFromCSR +} from "../certificate-common/certificate-csr-utils"; import { bufferToString, buildCertificateSubjectFromTemplate, buildSubjectAlternativeNamesFromTemplate, + convertExtendedKeyUsageArrayFromLegacy, convertExtendedKeyUsageArrayToLegacy, + convertKeyUsageArrayFromLegacy, convertKeyUsageArrayToLegacy, mapEnumsForValidation, normalizeDateForApi @@ -38,13 +51,19 @@ import { import { TCertificateFromProfileResponse, TCertificateOrderResponse, + TDisableRenewalConfigDTO, + TDisableRenewalResponse, TIssueCertificateFromProfileDTO, TOrderCertificateFromProfileDTO, - TSignCertificateFromProfileDTO + TRenewalConfigResponse, + TRenewCertificateDTO, + TSignCertificateFromProfileDTO, + TUpdateRenewalConfigDTO } from "./certificate-v3-types"; type TCertificateV3ServiceFactoryDep = { - certificateDAL: Pick; + certificateDAL: Pick; + certificateSecretDAL: Pick; certificateAuthorityDAL: Pick; certificateProfileDAL: Pick; certificateTemplateV2Service: Pick< @@ -95,6 +114,77 @@ const validateProfileAndPermissions = async ( return profile; }; +const validateRenewalEligibility = ( + certificate: { + id: string; + status: string; + notBefore: Date; + notAfter: Date; + revokedAt?: Date | null; + renewedByCertificateId?: string | null; + profileId?: string | null; + caId?: string | null; + pkiSubscriberId?: string | null; + }, + ca: TCertificateAuthorityWithAssociatedCa +) => { + const errors: string[] = []; + + if (certificate.status !== CertStatus.ACTIVE) { + errors.push(`Certificate status is ${certificate.status}, must be ${CertStatus.ACTIVE}`); + } + + const now = new Date(); + if (certificate.notAfter <= now) { + errors.push("Certificate is already expired"); + } + + if (certificate.revokedAt) { + errors.push("Certificate is revoked and cannot be renewed"); + } + + const caType = (ca.externalCa?.type as CaType) ?? CaType.INTERNAL; + const isInternalCa = caType === CaType.INTERNAL; + const isConnectedExternalCa = caType === CaType.ACME || caType === CaType.AZURE_AD_CS; + const isImportedCertificate = certificate.pkiSubscriberId != null && !certificate.profileId; + + if (!isInternalCa && !isConnectedExternalCa) { + errors.push(`CA type ${String(caType)} does not support renewal`); + } + + if (isImportedCertificate) { + errors.push("Externally imported certificates cannot be renewed"); + } + + if (ca.status !== CaStatus.ACTIVE) { + errors.push(`Certificate Authority is ${ca.status}, must be ${CaStatus.ACTIVE}`); + } + + if (certificate.renewedByCertificateId) { + errors.push("Certificate has already been renewed"); + } + + const certificateTtlInDays = Math.ceil( + (certificate.notAfter.getTime() - certificate.notBefore.getTime()) / (24 * 60 * 60 * 1000) + ); + + if (ca.internalCa?.notAfter) { + const caExpiryDate = new Date(ca.internalCa.notAfter); + const proposedCertExpiryDate = new Date(now.getTime() + certificateTtlInDays * 24 * 60 * 60 * 1000); + + if (proposedCertExpiryDate > caExpiryDate) { + errors.push( + `New certificate would expire (${proposedCertExpiryDate.toISOString()}) after its issuing CA (${caExpiryDate.toISOString()})` + ); + } + } + + return { + isEligible: errors.length === 0, + errors + }; +}; + const validateCaSupport = (ca: TCertificateAuthorityWithAssociatedCa, operation: string) => { const caType = (ca.externalCa?.type as CaType) ?? CaType.INTERNAL; if (caType !== CaType.INTERNAL) { @@ -129,11 +219,11 @@ const validateAlgorithmCompatibility = ( const keyType = parts[parts.length - 1]; if (caKeyAlgorithm.startsWith("RSA")) { - return keyType === "RSA"; + return keyType === CertKeyType.RSA; } if (caKeyAlgorithm.startsWith("EC")) { - return keyType === "ECDSA"; + return keyType === CertKeyType.ECDSA; } return false; @@ -155,8 +245,85 @@ const extractCertificateFromBuffer = (certData: Buffer | { rawData: Buffer } | s return bufferToString(certData as unknown as Buffer); }; +const parseKeyUsages = (keyUsages: unknown): CertKeyUsage[] => { + if (!keyUsages) return []; + if (Array.isArray(keyUsages)) return keyUsages as CertKeyUsage[]; + return (keyUsages as string).split(",").map((usage) => usage.trim() as CertKeyUsage); +}; + +const parseExtendedKeyUsages = (extendedKeyUsages: unknown): CertExtendedKeyUsage[] => { + if (!extendedKeyUsages) return []; + if (Array.isArray(extendedKeyUsages)) return extendedKeyUsages as CertExtendedKeyUsage[]; + return (extendedKeyUsages as string).split(",").map((usage) => usage.trim() as CertExtendedKeyUsage); +}; + +const isValidRenewalTiming = (renewBeforeDays: number, certificateExpiryDate: Date): boolean => { + const renewalDate = new Date(certificateExpiryDate.getTime() - renewBeforeDays * 24 * 60 * 60 * 1000); + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + tomorrow.setHours(0, 0, 0, 0); + + return renewalDate >= tomorrow; +}; + +const calculateRenewalThreshold = ( + profileRenewBeforeDays: number | undefined, + certificateTtlInDays: number +): number | undefined => { + if (!profileRenewBeforeDays) { + return undefined; + } + + if (certificateTtlInDays > profileRenewBeforeDays) { + return profileRenewBeforeDays; + } + + return Math.max(1, certificateTtlInDays - 1); +}; + +const parseTtlToDays = (ttl: string): number => { + const match = ttl.match(new RE2("^(\\d+)([dhm])$")); + if (!match) { + throw new BadRequestError({ message: `Invalid TTL format: ${ttl}` }); + } + + const [, value, unit] = match; + const numValue = parseInt(value, 10); + + switch (unit) { + case "d": + return numValue; + case "h": + return Math.ceil(numValue / 24); + case "m": + return Math.ceil(numValue / (24 * 60)); + default: + throw new BadRequestError({ message: `Unsupported TTL unit: ${unit}` }); + } +}; + +const calculateFinalRenewBeforeDays = ( + profile: { apiConfig?: { autoRenew?: boolean; renewBeforeDays?: number } }, + ttl: string, + certificateExpiryDate: Date +): number | undefined => { + if (!profile.apiConfig?.autoRenew || !profile.apiConfig.renewBeforeDays) { + return undefined; + } + + const certificateTtlInDays = parseTtlToDays(ttl); + const renewBeforeDays = calculateRenewalThreshold(profile.apiConfig.renewBeforeDays, certificateTtlInDays); + + if (!renewBeforeDays) { + return undefined; + } + + return isValidRenewalTiming(renewBeforeDays, certificateExpiryDate) ? renewBeforeDays : undefined; +}; + export const certificateV3ServiceFactory = ({ certificateDAL, + certificateSecretDAL, certificateAuthorityDAL, certificateProfileDAL, certificateTemplateV2Service, @@ -198,7 +365,8 @@ export const certificateV3ServiceFactory = ({ actorId, actorAuthMethod, actorOrgId, - templateId: profile.certificateTemplateId + templateId: profile.certificateTemplateId, + internal: true }); if (!template) { throw new NotFoundError({ message: "Certificate template not found for this profile" }); @@ -222,10 +390,6 @@ export const certificateV3ServiceFactory = ({ validateCaSupport(ca, "direct certificate issuance"); - if (!actorAuthMethod) { - throw new BadRequestError({ message: "Authentication method is required for certificate issuance" }); - } - validateAlgorithmCompatibility(ca, template); const effectiveSignatureAlgorithm = certificateRequest.signatureAlgorithm as CertSignatureAlgorithm | undefined; @@ -274,7 +438,16 @@ export const certificateV3ServiceFactory = ({ throw new NotFoundError({ message: "Certificate was issued but could not be found in database" }); } - await certificateDAL.updateById(cert.id, { profileId }); + const finalRenewBeforeDays = calculateFinalRenewBeforeDays( + profile, + certificateRequest.validity.ttl, + new Date(cert.notAfter) + ); + + await certificateDAL.updateById(cert.id, { + profileId, + renewBeforeDays: finalRenewBeforeDays + }); return { certificate: bufferToString(certificate), @@ -284,7 +457,8 @@ export const certificateV3ServiceFactory = ({ serialNumber, certificateId: cert.id, projectId: profile.projectId, - profileName: profile.slug + profileName: profile.slug, + commonName: cert.commonName || "" }; }; @@ -294,8 +468,6 @@ export const certificateV3ServiceFactory = ({ validity, notBefore, notAfter, - signatureAlgorithm, - keyAlgorithm, actor, actorId, actorAuthMethod, @@ -319,38 +491,40 @@ export const certificateV3ServiceFactory = ({ validateCaSupport(ca, "CSR signing"); - if (!actorAuthMethod) { - throw new BadRequestError({ message: "Authentication method is required for certificate signing" }); - } - const template = await certificateTemplateV2Service.getTemplateV2ById({ actor, actorId, actorAuthMethod, actorOrgId, - templateId: profile.certificateTemplateId + templateId: profile.certificateTemplateId, + internal: true }); if (!template) { throw new NotFoundError({ message: "Certificate template not found for this profile" }); } + const certificateRequest = extractCertificateRequestFromCSR(csr); + const mappedCertificateRequest = mapEnumsForValidation(certificateRequest); + + const { keyAlgorithm: extractedKeyAlgorithm, signatureAlgorithm: extractedSignatureAlgorithm } = + extractAlgorithmsFromCSR(csr); + + const validationResult = await certificateTemplateV2Service.validateCertificateRequest( + profile.certificateTemplateId, + mappedCertificateRequest + ); + + if (!validationResult.isValid) { + throw new BadRequestError({ + message: `Certificate request validation failed: ${validationResult.errors.join(", ")}` + }); + } + validateAlgorithmCompatibility(ca, template); - const effectiveSignatureAlgorithm = signatureAlgorithm; - const effectiveKeyAlgorithm = keyAlgorithm; - - if (template.algorithms?.keyAlgorithm && !effectiveKeyAlgorithm) { - throw new BadRequestError({ - message: "Key algorithm is required by template policy but not provided in request" - }); - } - - if (template.algorithms?.signature && !effectiveSignatureAlgorithm) { - throw new BadRequestError({ - message: "Signature algorithm is required by template policy but not provided in request" - }); - } + const effectiveSignatureAlgorithm = extractedSignatureAlgorithm; + const effectiveKeyAlgorithm = extractedKeyAlgorithm; const { certificate, certificateChain, issuingCaCertificate, serialNumber } = await internalCaService.signCertFromCa({ @@ -371,7 +545,12 @@ export const certificateV3ServiceFactory = ({ throw new NotFoundError({ message: "Certificate was signed but could not be found in database" }); } - await certificateDAL.updateById(cert.id, { profileId }); + const finalRenewBeforeDays = calculateFinalRenewBeforeDays(profile, validity.ttl, new Date(cert.notAfter)); + + await certificateDAL.updateById(cert.id, { + profileId, + renewBeforeDays: finalRenewBeforeDays + }); const certificateString = extractCertificateFromBuffer(certificate as unknown as Buffer); const certificateChainString = extractCertificateFromBuffer(certificateChain as unknown as Buffer); @@ -383,7 +562,8 @@ export const certificateV3ServiceFactory = ({ serialNumber, certificateId: cert.id, projectId: profile.projectId, - profileName: profile.slug + profileName: profile.slug, + commonName: cert.commonName || "" }; }; @@ -479,9 +659,405 @@ export const certificateV3ServiceFactory = ({ }); }; + const renewCertificate = async ({ + certificateId, + actor, + actorId, + actorAuthMethod, + actorOrgId, + internal = false + }: TRenewCertificateDTO & { internal?: boolean }): Promise => { + const renewalResult = await certificateDAL.transaction(async (tx) => { + const originalCert = await certificateDAL.findById(certificateId, tx); + if (!originalCert) { + throw new NotFoundError({ message: "Certificate not found" }); + } + + if (!originalCert.profileId) { + throw new ForbiddenRequestError({ + message: "Only certificates issued from a profile can be renewed" + }); + } + + const originalSignatureAlgorithm = originalCert.signatureAlgorithm as CertSignatureAlgorithm; + const originalKeyAlgorithm = originalCert.keyAlgorithm as CertKeyAlgorithm; + + if (!originalSignatureAlgorithm || !originalKeyAlgorithm) { + throw new BadRequestError({ + message: + "Original certificate does not have algorithm information stored. Cannot renew certificate issued before algorithm tracking was implemented." + }); + } + + const profile = await certificateProfileDAL.findByIdWithConfigs(originalCert.profileId); + if (!profile) { + throw new NotFoundError({ message: "Certificate profile not found" }); + } + + if (profile.enrollmentType !== EnrollmentType.API) { + throw new ForbiddenRequestError({ + message: "Certificate is not eligible for renewal: EST certificates cannot be renewed through this endpoint" + }); + } + + const certificateSecret = await certificateSecretDAL.findOne({ certId: originalCert.id }, tx); + if (!certificateSecret) { + throw new ForbiddenRequestError({ + message: + "Certificate is not eligible for renewal: certificates issued from CSR (external private key) cannot be renewed" + }); + } + + if (!internal) { + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: profile.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.CertificateManager + }); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateProfileActions.IssueCert, + ProjectPermissionSub.CertificateProfiles + ); + } + + const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId); + if (!ca) { + throw new NotFoundError({ message: "Certificate Authority not found" }); + } + + const eligibilityCheck = validateRenewalEligibility(originalCert, ca); + if (!eligibilityCheck.isEligible) { + await certificateDAL.updateById(originalCert.id, { + renewalError: `Certificate is not eligible for renewal: ${eligibilityCheck.errors.join(", ")}` + }); + throw new BadRequestError({ + message: `Certificate is not eligible for renewal: ${eligibilityCheck.errors.join(", ")}` + }); + } + + validateCaSupport(ca, "direct certificate issuance"); + + const template = await certificateTemplateV2Service.getTemplateV2ById({ + actor, + actorId, + actorAuthMethod, + actorOrgId, + templateId: profile.certificateTemplateId, + internal + }); + + if (!template) { + throw new NotFoundError({ message: "Certificate template not found for this profile" }); + } + + const originalTtlInDays = Math.ceil( + (new Date(originalCert.notAfter).getTime() - new Date(originalCert.notBefore).getTime()) / (1000 * 60 * 60 * 24) + ); + const ttl = `${originalTtlInDays}d`; + + const certificateRequest = { + commonName: originalCert.commonName || undefined, + keyUsages: convertKeyUsageArrayFromLegacy(parseKeyUsages(originalCert.keyUsages)), + extendedKeyUsages: convertExtendedKeyUsageArrayFromLegacy( + parseExtendedKeyUsages(originalCert.extendedKeyUsages) + ), + subjectAlternativeNames: originalCert.altNames + ? originalCert.altNames.split(",").map((san) => { + const trimmed = san.trim(); + + const isIpv4 = new RE2("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$").test(trimmed); + const isIpv6 = new RE2("^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$").test(trimmed); + if (isIpv4 || isIpv6) { + return { + type: CertSubjectAlternativeNameType.IP_ADDRESS, + value: trimmed + }; + } + + if (new RE2("^[^@]+@[^@]+\\.[^@]+$").test(trimmed)) { + return { + type: CertSubjectAlternativeNameType.EMAIL, + value: trimmed + }; + } + + if (new RE2("^[a-zA-Z][a-zA-Z0-9+.-]*:").test(trimmed)) { + return { + type: CertSubjectAlternativeNameType.URI, + value: trimmed + }; + } + + return { + type: CertSubjectAlternativeNameType.DNS_NAME, + value: trimmed + }; + }) + : [], + validity: { + ttl + }, + signatureAlgorithm: originalCert.signatureAlgorithm || undefined, + keyAlgorithm: originalCert.keyAlgorithm || undefined + }; + + const validationResult = await certificateTemplateV2Service.validateCertificateRequest( + profile.certificateTemplateId, + certificateRequest + ); + + if (!validationResult.isValid) { + await certificateDAL.updateById(originalCert.id, { + renewalError: `Template validation failed: ${validationResult.errors.join(", ")}` + }); + + throw new BadRequestError({ + message: `Certificate renewal failed. Errors: ${validationResult.errors.join(", ")}` + }); + } + + validateAlgorithmCompatibility(ca, template); + const notBefore = new Date(); + const notAfter = new Date(Date.now() + parseTtlToDays(ttl) * 24 * 60 * 60 * 1000); + + const finalRenewBeforeDays = calculateFinalRenewBeforeDays(profile, ttl, notAfter); + + const { certificate, certificateChain, issuingCaCertificate, serialNumber } = + await internalCaService.issueCertFromCa({ + caId: ca.id, + friendlyName: originalCert.friendlyName || originalCert.commonName || "Renewed Certificate", + commonName: originalCert.commonName || "", + altNames: originalCert.altNames || "", + ttl, + notBefore: normalizeDateForApi(notBefore), + notAfter: normalizeDateForApi(notAfter), + keyUsages: parseKeyUsages(originalCert.keyUsages), + extendedKeyUsages: parseExtendedKeyUsages(originalCert.extendedKeyUsages), + signatureAlgorithm: originalSignatureAlgorithm, + keyAlgorithm: originalKeyAlgorithm, + isFromProfile: true, + actor, + actorId, + actorAuthMethod, + actorOrgId, + internal: true, + tx + }); + + const newCert = await certificateDAL.findOne({ serialNumber, caId: ca.id }, tx); + if (!newCert) { + throw new NotFoundError({ message: "Certificate was signed but could not be found in database" }); + } + + await certificateDAL.updateById( + newCert.id, + { + profileId: originalCert.profileId, + renewBeforeDays: finalRenewBeforeDays, + renewedFromCertificateId: originalCert.id + }, + tx + ); + + await certificateDAL.updateById( + originalCert.id, + { + renewedByCertificateId: newCert.id, + renewalError: null + }, + tx + ); + + return { + certificate, + certificateChain, + issuingCaCertificate, + serialNumber, + newCert, + originalCert, + profile + }; + }); + + return { + certificate: renewalResult.certificate, + issuingCaCertificate: renewalResult.issuingCaCertificate, + certificateChain: renewalResult.certificateChain, + serialNumber: renewalResult.serialNumber, + certificateId: renewalResult.newCert.id, + projectId: renewalResult.profile.projectId, + profileName: renewalResult.profile.slug, + commonName: renewalResult.originalCert.commonName || "" + }; + }; + + const updateRenewalConfig = async ({ + certificateId, + renewBeforeDays, + actor, + actorId, + actorAuthMethod, + actorOrgId + }: TUpdateRenewalConfigDTO): Promise => { + const certificate = await certificateDAL.findById(certificateId); + if (!certificate) { + throw new NotFoundError({ message: "Certificate not found" }); + } + + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: certificate.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.CertificateManager + }); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateActions.Edit, + ProjectPermissionSub.Certificates + ); + + if (!certificate.profileId) { + throw new BadRequestError({ + message: "Certificate is not eligible for auto-renewal: certificate was not issued from a profile" + }); + } + + const profile = await certificateProfileDAL.findByIdWithConfigs(certificate.profileId); + if (!profile) { + throw new NotFoundError({ message: "Certificate profile not found" }); + } + + if (profile.enrollmentType !== EnrollmentType.API) { + throw new ForbiddenRequestError({ + message: "Certificate is not eligible for auto-renewal: EST certificates cannot be auto-renewed" + }); + } + + const certificateSecret = await certificateSecretDAL.findOne({ certId: certificate.id }); + if (!certificateSecret) { + throw new ForbiddenRequestError({ + message: + "Certificate is not eligible for auto-renewal: certificates issued from CSR (external private key) cannot be auto-renewed" + }); + } + + if (certificate.status !== CertStatus.ACTIVE) { + throw new BadRequestError({ + message: `Certificate is not eligible for auto-renewal: certificate status is ${certificate.status}, must be active` + }); + } + + const now = new Date(); + if (certificate.notAfter <= now) { + throw new BadRequestError({ + message: "Certificate is not eligible for auto-renewal: certificate has expired" + }); + } + + if (certificate.revokedAt) { + throw new BadRequestError({ + message: "Certificate is not eligible for auto-renewal: certificate has been revoked" + }); + } + + if (certificate.renewedByCertificateId) { + throw new BadRequestError({ + message: "Certificate is not eligible for auto-renewal: certificate has already been renewed" + }); + } + + const certificateTtlInDays = Math.ceil( + (new Date(certificate.notAfter).getTime() - new Date(certificate.notBefore).getTime()) / (24 * 60 * 60 * 1000) + ); + + if (renewBeforeDays >= certificateTtlInDays) { + throw new BadRequestError({ + message: "Invalid renewal configuration: renewal threshold exceeds certificate validity period" + }); + } + + if (!isValidRenewalTiming(renewBeforeDays, new Date(certificate.notAfter))) { + throw new BadRequestError({ + message: "Invalid renewal configuration: renewal would be triggered immediately or in the past" + }); + } + + await certificateDAL.updateById(certificateId, { + renewBeforeDays + }); + + return { + projectId: certificate.projectId, + renewBeforeDays, + commonName: certificate.commonName || "" + }; + }; + + const disableRenewalConfig = async ({ + certificateId, + actor, + actorId, + actorAuthMethod, + actorOrgId + }: TDisableRenewalConfigDTO): Promise => { + const certificate = await certificateDAL.findById(certificateId); + if (!certificate) { + throw new NotFoundError({ message: "Certificate not found" }); + } + + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: certificate.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.CertificateManager + }); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateActions.Edit, + ProjectPermissionSub.Certificates + ); + + if (!certificate.profileId) { + throw new BadRequestError({ + message: "Certificate is not eligible for auto-renewal: certificate was not issued from a profile" + }); + } + + const profile = await certificateProfileDAL.findByIdWithConfigs(certificate.profileId); + if (!profile) { + throw new NotFoundError({ message: "Certificate profile not found" }); + } + + if (profile.enrollmentType !== EnrollmentType.API) { + throw new ForbiddenRequestError({ + message: "Certificate is not eligible for auto-renewal: EST certificates cannot be auto-renewed" + }); + } + + await certificateDAL.updateById(certificateId, { + renewBeforeDays: null + }); + + return { + projectId: certificate.projectId, + commonName: certificate.commonName || "" + }; + }; + return { issueCertificateFromProfile, signCertificateFromProfile, - orderCertificateFromProfile + orderCertificateFromProfile, + renewCertificate, + updateRenewalConfig, + disableRenewalConfig }; }; diff --git a/backend/src/services/certificate-v3/certificate-v3-types.ts b/backend/src/services/certificate-v3/certificate-v3-types.ts index b54042c5c..a62a25b73 100644 --- a/backend/src/services/certificate-v3/certificate-v3-types.ts +++ b/backend/src/services/certificate-v3/certificate-v3-types.ts @@ -35,8 +35,6 @@ export type TSignCertificateFromProfileDTO = { }; notBefore?: Date; notAfter?: Date; - signatureAlgorithm?: string; - keyAlgorithm?: string; } & Omit; export type TOrderCertificateFromProfileDTO = { @@ -68,6 +66,7 @@ export type TCertificateFromProfileResponse = { certificateId: string; projectId: string; profileName: string; + commonName: string; }; export type TCertificateOrderResponse = { @@ -97,3 +96,27 @@ export type TCertificateOrderResponse = { projectId: string; profileName: string; }; + +export type TRenewCertificateDTO = { + certificateId: string; +} & Omit; + +export type TUpdateRenewalConfigDTO = { + certificateId: string; + renewBeforeDays: number; +} & Omit; + +export type TDisableRenewalConfigDTO = { + certificateId: string; +} & Omit; + +export type TRenewalConfigResponse = { + projectId: string; + renewBeforeDays: number; + commonName: string; +}; + +export type TDisableRenewalResponse = { + projectId: string; + commonName: string; +}; diff --git a/backend/src/services/certificate/certificate-dal.ts b/backend/src/services/certificate/certificate-dal.ts index 88808bd63..eb40b85a5 100644 --- a/backend/src/services/certificate/certificate-dal.ts +++ b/backend/src/services/certificate/certificate-dal.ts @@ -1,7 +1,7 @@ import { TDbClient } from "@app/db"; import { TableName, TCertificates } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; -import { ormify } from "@app/lib/knex"; +import { ormify, selectAllTableCols } from "@app/lib/knex"; import { CertStatus } from "./certificate-types"; @@ -114,12 +114,94 @@ export const certificateDALFactory = (db: TDbClient) => { } }; + const findCertificatesEligibleForRenewal = async ({ + limit, + offset + }: { + limit: number; + offset: number; + }): Promise<(TCertificates & { profileName?: string })[]> => { + try { + const now = new Date(); + const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999); + + const certs = (await db + .replicaNode()(TableName.Certificate) + .select(selectAllTableCols(TableName.Certificate)) + .select(db.ref("slug").withSchema(TableName.PkiCertificateProfile).as("profileName")) + .leftJoin( + TableName.PkiCertificateProfile, + `${TableName.Certificate}.profileId`, + `${TableName.PkiCertificateProfile}.id` + ) + .innerJoin(TableName.CertificateSecret, `${TableName.Certificate}.id`, `${TableName.CertificateSecret}.certId`) + .where(`${TableName.Certificate}.status`, CertStatus.ACTIVE) + .whereNull(`${TableName.Certificate}.renewedByCertificateId`) + .whereNull(`${TableName.Certificate}.renewalError`) + .whereNull(`${TableName.Certificate}.revokedAt`) + .whereNotNull(`${TableName.Certificate}.profileId`) + .whereNotNull(`${TableName.Certificate}.notAfter`) + .where(`${TableName.Certificate}.notAfter`, ">", now) + .whereNotNull(`${TableName.Certificate}.renewBeforeDays`) + .where(`${TableName.Certificate}.renewBeforeDays`, ">", 0) + .whereRaw( + `"${TableName.Certificate}"."notAfter" - INTERVAL '1 day' * "${TableName.Certificate}"."renewBeforeDays" <= ?`, + [endOfDay] + ) + .limit(limit) + .offset(offset) + .orderBy(`${TableName.Certificate}.notAfter`, "asc")) as TCertificates[]; + + return certs; + } catch (error) { + throw new DatabaseError({ error, name: "Find certificates eligible for renewal" }); + } + }; + + const findWithPrivateKeyInfo = async ( + filter: Partial, + options?: { offset?: number; limit?: number; sort?: [string, "asc" | "desc"][] } + ): Promise<(TCertificates & { hasPrivateKey: boolean })[]> => { + try { + let query = db + .replicaNode()(TableName.Certificate) + .leftJoin(TableName.CertificateSecret, `${TableName.Certificate}.id`, `${TableName.CertificateSecret}.certId`) + .select(selectAllTableCols(TableName.Certificate)) + .select(db.ref(`${TableName.CertificateSecret}.certId`).as("privateKeyRef")) + .where(filter); + + if (options?.offset) { + query = query.offset(options.offset); + } + if (options?.limit) { + query = query.limit(options.limit); + } + if (options?.sort) { + options.sort.forEach(([column, direction]) => { + query = query.orderBy(column, direction); + }); + } + + const results = await query; + return results.map((row) => { + return { + ...row, + hasPrivateKey: row.privateKeyRef !== null + }; + }); + } catch (error) { + throw new DatabaseError({ error, name: "Find certificates with private key info" }); + } + }; + return { ...certificateOrm, countCertificatesInProject, countCertificatesForPkiSubscriber, findLatestActiveCertForSubscriber, findAllActiveCertsForSubscriber, - findExpiredSyncedCertificates + findExpiredSyncedCertificates, + findCertificatesEligibleForRenewal, + findWithPrivateKeyInfo }; }; diff --git a/backend/src/services/certificate/certificate-types.ts b/backend/src/services/certificate/certificate-types.ts index 9da331be8..d654c96ba 100644 --- a/backend/src/services/certificate/certificate-types.ts +++ b/backend/src/services/certificate/certificate-types.ts @@ -21,6 +21,11 @@ export enum CertKeyAlgorithm { ECDSA_P521 = "EC_secp521r1" } +export enum CertKeyType { + RSA = "RSA", + ECDSA = "ECDSA" +} + export enum CertSignatureAlgorithm { RSA_SHA256 = "RSA-SHA256", RSA_SHA384 = "RSA-SHA384", diff --git a/backend/src/services/enrollment-config/api-enrollment-config-dal.ts b/backend/src/services/enrollment-config/api-enrollment-config-dal.ts index 1edfdae6c..ec4b8f76b 100644 --- a/backend/src/services/enrollment-config/api-enrollment-config-dal.ts +++ b/backend/src/services/enrollment-config/api-enrollment-config-dal.ts @@ -69,15 +69,15 @@ export const apiEnrollmentConfigDALFactory = (db: TDbClient) => { const profiles = await query .where((qb) => { void qb - .whereNull(`${TableName.PkiApiEnrollmentConfig}.autoRenewDays`) - .orWhere(`${TableName.PkiApiEnrollmentConfig}.autoRenewDays`, "<=", renewalThresholdDays); + .whereNull(`${TableName.PkiApiEnrollmentConfig}.renewBeforeDays`) + .orWhere(`${TableName.PkiApiEnrollmentConfig}.renewBeforeDays`, "<=", renewalThresholdDays); }) .select((tx || db).ref("id").withSchema(TableName.PkiCertificateProfile)) .select((tx || db).ref("name").withSchema(TableName.PkiCertificateProfile)) .select((tx || db).ref("projectId").withSchema(TableName.PkiCertificateProfile)) - .select((tx || db).ref("autoRenewDays").withSchema(TableName.PkiCertificateProfile)); + .select((tx || db).ref("renewBeforeDays").withSchema(TableName.PkiCertificateProfile)); - return profiles as Array<{ id: string; name: string; projectId: string; autoRenewDays?: number }>; + return profiles as Array<{ id: string; name: string; projectId: string; renewBeforeDays?: number }>; } catch (error) { throw new DatabaseError({ error, name: "Find profiles for auto renewal" }); } diff --git a/backend/src/services/enrollment-config/enrollment-config-types.ts b/backend/src/services/enrollment-config/enrollment-config-types.ts index 516f135fd..d2e03e4da 100644 --- a/backend/src/services/enrollment-config/enrollment-config-types.ts +++ b/backend/src/services/enrollment-config/enrollment-config-types.ts @@ -25,5 +25,5 @@ export interface TEstConfigData { export interface TApiConfigData { autoRenew: boolean; - autoRenewDays?: number; + renewBeforeDays?: number; } diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index dce0ed2cc..deee26e6c 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -154,7 +154,7 @@ type TProjectServiceFactoryDep = { >; pkiSubscriberDAL: Pick; certificateAuthorityDAL: Pick; - certificateDAL: Pick; + certificateDAL: Pick; certificateTemplateDAL: Pick; pkiAlertDAL: Pick; pkiCollectionDAL: Pick; @@ -951,13 +951,13 @@ export const projectServiceFactory = ({ ProjectPermissionSub.Certificates ); - const certificates = await certificateDAL.find( + const certificates = await certificateDAL.findWithPrivateKeyInfo( { projectId, ...(friendlyName && { friendlyName }), ...(commonName && { commonName }) }, - { offset, limit, sort: [["updatedAt", "desc"]] } + { offset, limit, sort: [["notAfter", "desc"]] } ); const count = await certificateDAL.countCertificatesInProject({ diff --git a/backend/src/services/secret-sync/github/github-sync-fns.ts b/backend/src/services/secret-sync/github/github-sync-fns.ts index 4b174ca2a..e7116d604 100644 --- a/backend/src/services/secret-sync/github/github-sync-fns.ts +++ b/backend/src/services/secret-sync/github/github-sync-fns.ts @@ -211,10 +211,18 @@ export const GithubSyncFns = { } const { connection } = secretSync; - const token = - connection.method === GitHubConnectionMethod.OAuth - ? connection.credentials.accessToken - : await getGitHubAppAuthToken(connection, gatewayService, gatewayV2Service); + let token: string; + + switch (connection.method) { + case GitHubConnectionMethod.OAuth: + token = connection.credentials.accessToken; + break; + case GitHubConnectionMethod.Pat: + token = connection.credentials.personalAccessToken; + break; + default: + token = await getGitHubAppAuthToken(connection, gatewayService, gatewayV2Service); + } const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService, gatewayV2Service); const publicKey = await getPublicKey(secretSync, gatewayService, gatewayV2Service, token); @@ -269,10 +277,18 @@ export const GithubSyncFns = { const secretMap = Object.fromEntries(Object.entries(ogSecretMap).map(([i, v]) => [i.toUpperCase(), v])); const { connection } = secretSync; - const token = - connection.method === GitHubConnectionMethod.OAuth - ? connection.credentials.accessToken - : await getGitHubAppAuthToken(connection, gatewayService, gatewayV2Service); + let token: string; + + switch (connection.method) { + case GitHubConnectionMethod.OAuth: + token = connection.credentials.accessToken; + break; + case GitHubConnectionMethod.Pat: + token = connection.credentials.personalAccessToken; + break; + default: + token = await getGitHubAppAuthToken(connection, gatewayService, gatewayV2Service); + } const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService, gatewayV2Service); diff --git a/backend/vitest.e2e.config.ts b/backend/vitest.e2e.config.mts similarity index 86% rename from backend/vitest.e2e.config.ts rename to backend/vitest.e2e.config.mts index bb7ae8087..83554b818 100644 --- a/backend/vitest.e2e.config.ts +++ b/backend/vitest.e2e.config.mts @@ -12,13 +12,16 @@ export default defineConfig({ }, environment: "./e2e-test/vitest-environment-knex.ts", include: ["./e2e-test/**/*.spec.ts"], + pool: "threads", poolOptions: { threads: { - singleThread: true, - useAtomics: true, - isolate: false + minThreads: 1, + maxThreads: 1, + singleThread: true } }, + fileParallelism: false, + alias: { "./license-fns": path.resolve(__dirname, "./src/ee/services/license/__mocks__/license-fns") } diff --git a/backend/vitest.unit.config.ts b/backend/vitest.unit.config.mts similarity index 100% rename from backend/vitest.unit.config.ts rename to backend/vitest.unit.config.mts diff --git a/docs/cli/usage.mdx b/docs/cli/usage.mdx index 8d0236ed5..bedfda22c 100644 --- a/docs/cli/usage.mdx +++ b/docs/cli/usage.mdx @@ -9,11 +9,15 @@ The CLI is designed for a variety of secret management applications ranging from In the following steps, we explore how to use the Infisical CLI to fetch back environment variables from Infisical and inject them into your local development process. - + + + If you prefer learning by watching, you can follow along our step-by-step video tutorial [here](https://www.youtube.com/watch?v=EzDQC7nY3YY). + + Start by running the `infisical login` command to authenticate with Infisical. - + ```bash infisical login ``` @@ -23,7 +27,7 @@ The CLI is designed for a variety of secret management applications ranging from Next, navigate to your project and initialize Infisical. - + ```bash # navigate to your project cd /path/to/project @@ -123,23 +127,25 @@ The CLI is designed for a variety of secret management applications ranging from Starting with CLI version v0.4.0, you can now choose to log in via Infisical Cloud (US/EU) or your own self-hosted instance by simply running `infisical login` and following the on-screen instructions — no need to manually set the `INFISICAL_API_URL` environment variable. - For versions prior to v0.4.0, the CLI defaults to the US Cloud. To connect to the EU Cloud or a self-hosted instance, set the `INFISICAL_API_URL` environment variable to `https://eu.infisical.com` or your custom URL. +For versions prior to v0.4.0, the CLI defaults to the US Cloud. To connect to the EU Cloud or a self-hosted instance, set the `INFISICAL_API_URL` environment variable to `https://eu.infisical.com` or your custom URL. + ## Custom Request Headers - The Infisical CLI supports custom HTTP headers for requests to servers protected by authentication services such as Cloudflare Access. Configure these headers using the `INFISICAL_CUSTOM_HEADERS` environment variable: +The Infisical CLI supports custom HTTP headers for requests to servers protected by authentication services such as Cloudflare Access. Configure these headers using the `INFISICAL_CUSTOM_HEADERS` environment variable: - ```bash - # Syntax: headername1=headervalue1 headername2=headervalue2 - export INFISICAL_CUSTOM_HEADERS="Access-Client-Id=your-client-id Access-Client-Secret=your-client-secret" +```bash +# Syntax: headername1=headervalue1 headername2=headervalue2 +export INFISICAL_CUSTOM_HEADERS="Access-Client-Id=your-client-id Access-Client-Secret=your-client-secret" - # Execute Infisical commands after setting the environment variable - infisical secrets - ``` +# Execute Infisical commands after setting the environment variable +infisical secrets +``` + +This functionality enables secure interaction with Infisical instances that require specific authentication headers. - This functionality enables secure interaction with Infisical instances that require specific authentication headers. ## History diff --git a/docs/docs.json b/docs/docs.json index 46f5aaf70..dda9adcfc 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -176,16 +176,14 @@ "pages": [ "documentation/platform/gateways/overview", "documentation/platform/gateways/gateway-deployment", - "documentation/platform/gateways/relay-deployment", - "documentation/platform/gateways/security", { - "group": "Gateway (Deprecated)", + "group": "Relay Deployment", "pages": [ - "documentation/platform/gateways-deprecated/overview", - "documentation/platform/gateways-deprecated/gateway-security", - "documentation/platform/gateways-deprecated/networking" + "documentation/platform/gateways/relay-deployment/overview", + "documentation/platform/gateways/relay-deployment/terraform" ] - } + }, + "documentation/platform/gateways/security" ] } ] diff --git a/docs/documentation/platform/gateways-deprecated/gateway-security.mdx b/docs/documentation/platform/gateways-deprecated/gateway-security.mdx deleted file mode 100644 index 93a7f662f..000000000 --- a/docs/documentation/platform/gateways-deprecated/gateway-security.mdx +++ /dev/null @@ -1,91 +0,0 @@ ---- -title: "Gateway Security Architecture" -sidebarTitle: "Architecture" -description: "Understand the security model and tenant isolation of Infisical's Gateway" ---- - -# Gateway Security Architecture - -The Infisical Gateway enables Infisical Cloud to securely interact with private resources using mutual TLS authentication and private PKI (Public Key Infrastructure) system to ensure secure, isolated communication between multiple tenants. -This document explains the internal security architecture and how tenant isolation is maintained. - -## Security Model Overview - -### Private PKI System -Each organization (tenant) in Infisical has its own private PKI system consisting of: - -1. **Root CA**: The ultimate trust anchor for the organization -2. **Intermediate CAs**: - - Client CA: Issues certificates for cloud components - - Gateway CA: Issues certificates for gateway instances - -This hierarchical structure ensures complete isolation between organizations as each has its own independent certificate chain. - -### Certificate Hierarchy -``` -Root CA (Organization Specific) -├── Client CA -│ └── Client Certificates (Cloud Components) -└── Gateway CA - └── Gateway Certificates (Gateway Instances) -``` - -## Communication Security - -### 1. Gateway Registration -When a gateway is first deployed: - -1. Establishes initial connection using machine identity token -2. Allocates a relay address for communication -3. Exchanges certificates through a secure handshake: - - Gateway receives a unique certificate signed by organization's Gateway CA along with certificate chain for verification - -### 2. Mutual TLS Authentication -All communication between gateway and cloud uses mutual TLS (mTLS): - -- **Gateway Authentication**: - - Presents certificate signed by organization's Gateway CA - - Certificate contains unique identifiers (Organization ID, Gateway ID) - - Cloud validates complete certificate chain - -- **Cloud Authentication**: - - Presents certificate signed by organization's Client CA - - Certificate includes required organizational unit ("gateway-client") - - Gateway validates certificate chain back to organization's root CA - -### 3. Relay Communication -The relay system provides secure tunneling: - -1. **Connection Establishment**: - - Uses QUIC protocol over UDP for efficient, secure communication - - Provides built-in encryption, congestion control, and multiplexing - - Enables faster connection establishment and reduced latency - - Each organization's traffic is isolated using separate relay sessions - -2. **Traffic Isolation**: - - Each gateway gets unique relay credentials - - Traffic is end-to-end encrypted using QUIC's TLS 1.3 - - Organization's private keys never leave their environment - -## Tenant Isolation - -### Certificate-Based Isolation -- Each organization has unique root CA and intermediate CAs -- Certificates contain organization-specific identifiers -- Cross-tenant communication is cryptographically impossible - -### Gateway-Project Mapping -- Gateways are explicitly mapped to specific projects -- Access controls enforce organization boundaries -- Project-level permissions determine resource accessibility - -### Resource Access Control -1. **Project Verification**: - - Gateway verifies project membership - - Validates organization ownership - - Enforces project-level permissions - -2. **Resource Restrictions**: - - Gateways only accept connections to approved resources - - Each connection requires explicit project authorization - - Resources remain private to their assigned organization diff --git a/docs/documentation/platform/gateways-deprecated/images/gateway-highlevel-diagram.png b/docs/documentation/platform/gateways-deprecated/images/gateway-highlevel-diagram.png deleted file mode 100644 index 5f942bcf0..000000000 Binary files a/docs/documentation/platform/gateways-deprecated/images/gateway-highlevel-diagram.png and /dev/null differ diff --git a/docs/documentation/platform/gateways-deprecated/networking.mdx b/docs/documentation/platform/gateways-deprecated/networking.mdx deleted file mode 100644 index 51a81ee42..000000000 --- a/docs/documentation/platform/gateways-deprecated/networking.mdx +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: "Networking" -description: "Network configuration and firewall requirements for Infisical Gateway" ---- - -The Infisical Gateway requires outbound network connectivity to establish secure communication with Infisical's relay infrastructure. -This page outlines the required ports, protocols, and firewall configurations needed for optimal gateway usage. - -## Network Architecture - -The gateway uses a relay-based architecture to establish secure connections: - -1. **Gateway** connects outbound to **Relay Servers** using UDP/QUIC protocol -2. **Relay Servers** facilitate secure communication between Gateway and Infisical Cloud -3. All traffic is end-to-end encrypted using mutual TLS over QUIC - -## Required Network Connectivity - -### Outbound Connections (Required) - -The gateway requires the following outbound connectivity: - -| Protocol | Destination | Ports | Purpose | -|----------|-------------|-------|---------| -| UDP | Relay Servers | 49152-65535 | Allocated relay communication (TLS) | -| TCP | app.infisical.com / eu.infisical.com | 443 | API communication and relay allocation | - -### Relay Server IP Addresses - -Your firewall must allow outbound connectivity to the following Infisical relay servers on dynamically allocated ports. - - - - ``` - 54.235.197.91:49152-65535 - 18.215.196.229:49152-65535 - 3.222.120.233:49152-65535 - 34.196.115.157:49152-65535 - ``` - - - ``` - 3.125.237.40:49152-65535 - 52.28.157.98:49152-65535 - 3.125.176.90:49152-65535 - ``` - - - Please contact your Infisical account manager for dedicated relay server IP addresses. - - - - - These IP addresses are static and managed by Infisical. Any changes will be communicated with 60-day advance notice. - - -## Protocol Details - -### QUIC over UDP - -The gateway uses QUIC (Quick UDP Internet Connections) for primary communication: - -- **Port 5349**: STUN/TURN over TLS (secure relay communication) -- **Built-in features**: Connection migration, multiplexing, reduced latency -- **Encryption**: TLS 1.3 with certificate pinning - -## Understanding Firewall Behavior with UDP - -Unlike TCP connections, UDP is a stateless protocol, and depending on your organization's firewall configuration, you may need to adjust network rules accordingly. -When the gateway sends UDP packets to a relay server, the return responses need to be allowed back through the firewall. -Modern firewalls handle this through "connection tracking" (also called "stateful inspection"), but the behavior can vary depending on your firewall configuration. - - -### Connection Tracking - -Modern firewalls automatically track UDP connections and allow return responses. This is the preferred configuration as it: -- Automatically handles return responses -- Reduces firewall rule complexity -- Avoids the need for manual IP whitelisting - -In the event that your firewall does not support connection tracking, you will need to whitelist the relay IPs to explicitly define return traffic manually. - -## Common Network Scenarios - -### Corporate Firewalls - -For corporate environments with strict egress filtering: - -1. **Whitelist relay IP addresses** (listed above) -2. **Allow UDP port 5349** outbound -3. **Configure connection tracking** for UDP return traffic -4. **Allow ephemeral port range** 49152-65535 for return traffic if connection tracking is disabled - -### Cloud Environments (AWS/GCP/Azure) - -Configure security groups to allow: -- **Outbound UDP** to relay IPs on port 5349 -- **Outbound HTTPS** to app.infisical.com/eu.infisical.com on port 443 -- **Inbound UDP** on ephemeral ports (if not using stateful rules) - -## Frequently Asked Questions - - - -The gateway is designed to handle network interruptions gracefully: - -- **Automatic reconnection**: The gateway will automatically attempt to reconnect to relay servers every 5 seconds if the connection is lost -- **Connection retry logic**: Built-in retry mechanisms handle temporary network outages without manual intervention -- **Multiple relay servers**: If one relay server is unavailable, the gateway can connect to alternative relay servers -- **Persistent sessions**: Existing connections are maintained where possible during brief network interruptions -- **Graceful degradation**: The gateway logs connection issues and continues attempting to restore connectivity - -No manual intervention is typically required during network interruptions. - - - -QUIC (Quick UDP Internet Connections) provides several advantages over traditional TCP for gateway communication: - -- **Faster connection establishment**: QUIC combines transport and security handshakes, reducing connection setup time -- **Built-in encryption**: TLS 1.3 is integrated into the protocol, ensuring all traffic is encrypted by default -- **Connection migration**: QUIC connections can survive IP address changes (useful for NAT rebinding) -- **Reduced head-of-line blocking**: Multiple data streams can be multiplexed without blocking each other -- **Better performance over unreliable networks**: Advanced congestion control and packet loss recovery -- **Lower latency**: Optimized for real-time communication between gateway and cloud services - -While TCP is stateful and easier for firewalls to track, QUIC's performance benefits outweigh the additional firewall configuration requirements. - - - -No inbound ports need to be opened. The gateway only makes outbound connections: - -- **Outbound UDP** to relay servers on ports 49152-65535 -- **Outbound HTTPS** to Infisical API endpoints -- **Return responses** are handled by connection tracking or explicit IP whitelisting - -This design maintains security by avoiding the need for inbound firewall rules that could expose your network to external threats. - - - -If your firewall has strict UDP restrictions: - -1. **Work with your network team** to allow outbound UDP to the specific relay IP addresses -2. **Use explicit IP whitelisting** if connection tracking is disabled -3. **Consider network policy exceptions** for the gateway host -4. **Monitor firewall logs** to identify which specific rules are blocking traffic - -The gateway requires UDP connectivity to function - TCP-only configurations are not supported. - - - -The gateway connects to **one relay server at a time**: - -- **Single active connection**: Only one relay connection is established per gateway instance -- **Automatic failover**: If the current relay becomes unavailable, the gateway will connect to an alternative relay -- **Load distribution**: Different gateway instances may connect to different relay servers for load balancing -- **No manual selection**: The Infisical API automatically assigns the optimal relay server based on availability and proximity - -You should whitelist all relay IP addresses to ensure proper failover functionality. - - -No, relay servers cannot decrypt any traffic passing through them: - -- **End-to-end encryption**: All traffic between the gateway and Infisical Cloud is encrypted using mutual TLS with certificate pinning -- **Relay acts as a tunnel**: The relay server only forwards encrypted packets - it has no access to encryption keys -- **No data storage**: Relay servers do not store any traffic or network-identifiable information -- **Certificate isolation**: Each organization has its own private PKI system, ensuring complete tenant isolation - -The relay infrastructure is designed as a secure forwarding mechanism, similar to a VPN tunnel, where the relay provider cannot see the contents of the traffic flowing through it. - - diff --git a/docs/documentation/platform/gateways-deprecated/overview.mdx b/docs/documentation/platform/gateways-deprecated/overview.mdx deleted file mode 100644 index f81809f7b..000000000 --- a/docs/documentation/platform/gateways-deprecated/overview.mdx +++ /dev/null @@ -1,352 +0,0 @@ ---- -title: "Gateway" -sidebarTitle: "Overview" -description: "How to access private network resources from Infisical" ---- - -![Alt text](/documentation/platform/gateways-deprecated/images/gateway-highlevel-diagram.png) - -The Infisical Gateway provides secure access to private resources within your network without needing direct inbound connections to your environment. -This method keeps your resources fully protected from external access while enabling Infisical to securely interact with resources like databases. -Common use cases include generating dynamic credentials or rotating credentials for private databases. - - - Gateway is a paid feature available under the Enterprise Tier for Infisical - Cloud users. Self-hosted Infisical users can contact - [sales@infisical.com](mailto:sales@infisical.com) to purchase an enterprise - license. - - -## How It Works - -The Gateway serves as a secure intermediary that facilitates direct communication between the Infisical server and your private network. -It’s a lightweight daemon packaged within the Infisical CLI, making it easy to deploy and manage. Once set up, the Gateway establishes a connection with a relay server, ensuring that all communication between Infisical and your Gateway is fully end-to-end encrypted. -This setup guarantees that only the platform and your Gateway can decrypt the transmitted information, keeping communication with your resources secure, private and isolated. - -## Deployment - -The Infisical Gateway is seamlessly integrated into the Infisical CLI under the `gateway` command, making it simple to deploy and manage. -You can install the Gateway in all the same ways you install the Infisical CLI—whether via npm, Docker, or a binary. -For detailed installation instructions, refer to the Infisical [CLI Installation instructions](/cli/overview). - -To function, the Gateway must authenticate with Infisical. This requires a machine identity configured with the appropriate permissions to create and manage a Gateway. -Once authenticated, the Gateway establishes a secure connection with Infisical to allow your private resources to be reachable. - -### Get started - - - - 1. Navigate to **Organization Access Control** in your Infisical dashboard. - 2. Create a dedicated machine identity for your Gateway. - 3. **Best Practice:** Assign a unique identity to each Gateway for better security and management. - ![Create Gateway Identity](../../../images/platform/gateways/create-identity-for-gateway.png) - - - - You'll need to choose an authentication method to initiate communication with Infisical. View the available machine identity authentication methods [here](/documentation/platform/identities/machine-identities). - - - - Use the Infisical CLI to deploy the Gateway. You can run it directly or install it as a systemd service for production: - - - - For production deployments on Linux, install the Gateway as a systemd service: - ```bash - sudo infisical gateway install --token --domain - sudo systemctl start infisical-gateway - ``` - This will install and start the Gateway as a secure systemd service that: - - Runs with restricted privileges: - - Runs as root user (required for secure token management) - - Restricted access to home directories - - Private temporary directory - - Automatically restarts on failure - - Starts on system boot - - Manages token and domain configuration securely in `/etc/infisical/gateway.conf` - - - The install command requires: - - Linux operating system - - Root/sudo privileges - - Systemd - - - - - - The Gateway can be installed via [Helm](https://helm.sh/). Helm is a package manager for Kubernetes that allows you to define, install, and upgrade Kubernetes applications. - - For production deployments on Kubernetes, install the Gateway using the Infisical Helm chart: - - ### Install the latest Helm Chart repository - ```bash - helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/' - ``` - - ### Update the Helm Chart repository - ```bash - helm repo update - ``` - - ### Create a Kubernetes Secret containing gateway environment variables - - The gateway supports all identity authentication methods through the use of environment variables. - The environment variables must be set in the `infisical-gateway-environment` Kubernetes secret. - - - #### Supported authentication methods - - - - The Universal Auth method is a simple and secure way to authenticate with Infisical. It requires a client ID and a client secret to authenticate with Infisical. - - - - - Your machine identity client ID. - - - Your machine identity client secret. - - - The authentication method to use. Must be `universal-auth` when using Universal Auth. - - - - - ```bash - kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=universal-auth --from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_ID= --from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET= - ``` - - - - The Native Kubernetes method is used to authenticate with Infisical when running in a Kubernetes environment. It requires a service account token to authenticate with Infisical. - - - - - Your machine identity ID. - - - Path to the Kubernetes service account token to use. Default: `/var/run/secrets/kubernetes.io/serviceaccount/token`. - - - The authentication method to use. Must be `kubernetes` when using Native Kubernetes. - - - - - - ```bash - kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=kubernetes --from-literal=INFISICAL_MACHINE_IDENTITY_ID= - ``` - - - - The Native Azure method is used to authenticate with Infisical when running in an Azure environment. - - - - - Your machine identity ID. - - - The authentication method to use. Must be `azure` when using Native Azure. - - - - - ```bash - kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=azure --from-literal=INFISICAL_MACHINE_IDENTITY_ID= - ``` - - - The Native GCP ID Token method is used to authenticate with Infisical when running in a GCP environment. - - - - - Your machine identity ID. - - - The authentication method to use. Must be `gcp-id-token` when using Native GCP ID Token. - - - - - ```bash - kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=gcp-id-token --from-literal=INFISICAL_MACHINE_IDENTITY_ID= - ``` - - - - The GCP IAM method is used to authenticate with Infisical with a GCP service account key. - - - - - Your machine identity ID. - - - Path to your GCP service account key file _(Must be in JSON format!)_ - - - The authentication method to use. Must be `gcp-iam` when using GCP IAM. - - - - - ```bash - kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=gcp-iam --from-literal=INFISICAL_MACHINE_IDENTITY_ID= --from-literal=INFISICAL_GCP_SERVICE_ACCOUNT_KEY_FILE_PATH= - ``` - - - - - The AWS IAM method is used to authenticate with Infisical with an AWS IAM role while running in an AWS environment like EC2, Lambda, etc. - - - - - Your machine identity ID. - - - The authentication method to use. Must be `aws-iam` when using Native AWS IAM. - - - - - ```bash - kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=aws-iam --from-literal=INFISICAL_MACHINE_IDENTITY_ID= - ``` - - - - The OIDC Auth method is used to authenticate with Infisical via identity tokens with OIDC. - - - - - Your machine identity ID. - - - The OIDC JWT from the identity provider. - - - The authentication method to use. Must be `oidc-auth` when using OIDC Auth. - - - - - ```bash - kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=oidc-auth --from-literal=INFISICAL_MACHINE_IDENTITY_ID= --from-literal=INFISICAL_JWT= - ``` - - - - The JWT Auth method is used to authenticate with Infisical via a JWT token. - - - - - The JWT token to use for authentication. - - - Your machine identity ID. - - - The authentication method to use. Must be `jwt-auth` when using JWT Auth. - - - - - ```bash - kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=jwt-auth --from-literal=INFISICAL_JWT= --from-literal=INFISICAL_MACHINE_IDENTITY_ID= - ``` - - - You can use the `INFISICAL_TOKEN` environment variable to authenticate with Infisical with a raw machine identity access token. - - - - - The machine identity access token to use for authentication. - - - - - ```bash - kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_TOKEN= - ``` - - - - - #### Other environment variables - - - - The API URL to use for the gateway. By default, `INFISICAL_API_URL` is set to `https://app.infisical.com`. - - - - - ### Install the Infisical Gateway Helm Chart - ```bash - helm install infisical-gateway infisical-helm-charts/infisical-gateway - ``` - - ### Check the gateway logs - After installing the gateway, you can check the logs to ensure it's running as expected. - - ```bash - kubectl logs deployment/infisical-gateway - ``` - - You should see the following output which indicates the gateway is running as expected. - ```bash - $ kubectl logs deployment/infisical-gateway - INF Provided relay port 5349. Using TLS - INF Connected with relay - INF 10.0.101.112:56735 - INF Starting relay connection health check - INF Gateway started successfully - INF New connection from: 10.0.1.8:34051 - INF Gateway is reachable by Infisical - ``` - - - - - For development or testing, you can run the Gateway directly. Log in with your machine identity and start the Gateway in one command: - ```bash - infisical gateway --token $(infisical login --method=universal-auth --client-id=<> --client-secret=<> --plain) - ``` - - Alternatively, if you already have the token, use it directly with the `--token` flag: - ```bash - infisical gateway --token - ``` - - Or set it as an environment variable: - ```bash - export INFISICAL_TOKEN= - infisical gateway - ``` - - - - For detailed information about the gateway command and its options, see the [gateway command documentation](/cli/commands/gateway). - - - Ensure the deployed Gateway has network access to the private resources you intend to connect with Infisical. - - - - - - To confirm your Gateway is working, check the deployment status by looking for the message **"Gateway started successfully"** in the Gateway logs. This indicates the Gateway is running properly. Next, verify its registration by opening your Infisical dashboard, navigating to **Organization Access Control**, and selecting the **Gateways** tab. Your newly deployed Gateway should appear in the list. - ![Gateway List](../../../images/platform/gateways/gateway-list.png) - - diff --git a/docs/documentation/platform/gateways/relay-deployment.mdx b/docs/documentation/platform/gateways/relay-deployment/overview.mdx similarity index 95% rename from docs/documentation/platform/gateways/relay-deployment.mdx rename to docs/documentation/platform/gateways/relay-deployment/overview.mdx index 767cf3732..ba7689196 100644 --- a/docs/documentation/platform/gateways/relay-deployment.mdx +++ b/docs/documentation/platform/gateways/relay-deployment/overview.mdx @@ -1,5 +1,5 @@ --- -title: "Relay Deployment" +title: "Overview" description: "How to deploy Infisical Relay Servers" --- @@ -107,13 +107,8 @@ To successfully deploy an Infisical Relay for use, follow these steps in order. - - Install the Infisical CLI on the server where you plan to deploy the relay. The CLI is required for relay installation and management. - - See the [CLI Installation Guide](/cli/overview) for instructions. - - This server must have a static IP address or DNS name to be identifiable by the Infisical platform. - + + Provision a server or virtual machine where you plan to deploy the relay. This server must have a static IP address or DNS name to be identifiable by the Infisical platform. @@ -133,6 +128,8 @@ To successfully deploy an Infisical Relay for use, follow these steps in order. + You can deploy the Infisical Relay in various ways. This guide provides a manual setup example using the Infisical CLI. For an infrastructure-as-code approach, see our [Terraform guide](/documentation/platform/gateways/relay-deployment/terraform). + The Infisical CLI is used to install and start the relay in your chosen environment. The CLI provides commands for both production and development scenarios, and supports a variety of options/flags to configure your deployment. To view all available flags and equivalent environment variables for relay deployment, see the [Relay CLI Command Reference](/cli/commands/relay). diff --git a/docs/documentation/platform/gateways/relay-deployment/terraform.mdx b/docs/documentation/platform/gateways/relay-deployment/terraform.mdx new file mode 100644 index 000000000..e89871cd9 --- /dev/null +++ b/docs/documentation/platform/gateways/relay-deployment/terraform.mdx @@ -0,0 +1,151 @@ +--- +title: "Terraform" +description: "How to deploy Infisical Relay Servers using Terraform" +--- + +This guide walks you through deploying an Infisical Relay server using Terraform. Select a provider below for specific instructions. + + + +The provided configuration automates the creation of the EC2 instance, sets up the necessary security group rules, and uses a startup script to install and configure the Infisical Relay service. + +### Prerequisites + +Before you start, make sure you have the following: +- An AWS account with permissions to create EC2 instances, Security Groups, and Elastic IPs. +- An existing VPC and Subnet ID in your desired AWS region. +- The AMI ID for your chosen OS (this guide uses an Ubuntu 22.04 LTS AMI). +- Credentials for the Infisical Relay to authenticate with your Infisical instance. This guide uses a Machine Identity token, but other methods are available. You can find a full list of authentication options [here](/cli/commands/relay#available-authentication-methods). + +### Terraform Configuration + +Here is the complete Terraform configuration to deploy the Infisical Relay. + +```terraform +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = "us-west-2" # Change to your desired AWS region +} + +# Security Group for the Infisical Relay instance +resource "aws_security_group" "infisical_relay_sg" { + name = "infisical-relay-sg" + description = "Allows inbound traffic for Infisical Relay and SSH" + vpc_id = "vpc-0c71f9c5709d88d18" # Change to your VPC ID + + # Inbound: Allows the Infisical platform to securely communicate with the Relay server. + ingress { + from_port = 8443 + to_port = 8443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Inbound: Allows Infisical Gateway to securely communicate via the Relay. + ingress { + from_port = 2222 + to_port = 2222 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Inbound: Allows secure shell (SSH) access for administration. + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] # Restrict this to your IP in production + } + + # Outbound: Allows the Relay server to make necessary outbound connections to the Infisical platform. + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "infisical-relay-sg" + } +} + +# Elastic IP for a static public IP address +resource "aws_eip" "infisical_relay_eip" { + tags = { + Name = "infisical-relay-eip" + } +} + +# EC2 instance to run Infisical Relay +module "infisical_relay_instance" { + source = "terraform-aws-modules/ec2-instance/aws" + version = "~> 5.6" + + name = "infisical-relay-example" + ami = "ami-065778886ef8ec7c8" # Change to your desired AMI ID + instance_type = "t3.micro" + subnet_id = "subnet-0fd2337a1c604a494" # Change to your Subnet ID + + vpc_security_group_ids = [aws_security_group.infisical_relay_sg.id] + associate_public_ip_address = false # We are using an Elastic IP instead + + user_data = <<-EOT + #!/bin/bash + set -e + # Install Infisical CLI + curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash + apt-get update && apt-get install -y infisical + + # Install the relay as a systemd service. + # This example uses a Machine Identity token for authentication via the INFISICAL_TOKEN environment variable. + # + # Note: For production environments, you might consider fetching the token from AWS Parameter Store or AWS Secrets Manager. + export INFISICAL_TOKEN="your-machine-identity-token" + sudo -E infisical relay systemd install \ + --name "my-relay-example" \ + --domain "https://app.infisical.com" \ + --host "${aws_eip.infisical_relay_eip.public_ip}" + + # Start and enable the service to run on boot + sudo systemctl start infisical-relay + sudo systemctl enable infisical-relay + EOT +} + +# Associate the Elastic IP with the EC2 instance +resource "aws_eip_association" "eip_assoc" { + instance_id = module.infisical_relay_instance.id + allocation_id = aws_eip.infisical_relay_eip.id +} +``` + + +The provided security group rules are open to the internet (`0.0.0.0/0`) for simplicity. In a production environment, you should restrict the `cidr_blocks` to known IP addresses for enhanced security, especially for the SSH port (22). + + +### How to Deploy + +1. **Save the configuration:** Save the code above to a file named `main.tf`. +2. **Customize values:** Update the placeholder values in `main.tf` to match your AWS environment and Infisical credentials. You'll need to replace: + - `region` in the `provider` block. + - `vpc_id` in the `aws_security_group` resource. + - `ami` and `subnet_id` in the `infisical_relay_instance` module. + - The `INFISICAL_TOKEN` environment variable in the `user_data` script (e.g., `export INFISICAL_TOKEN="your-machine-identity-token"`). + - The `--domain` in the `user_data` script if you are self-hosting Infisical. +3. **Apply the configuration:** Run the following Terraform commands in your terminal: + ```bash + terraform init + terraform plan + terraform apply + ``` + + diff --git a/docs/documentation/platform/kms/hsm-integration.mdx b/docs/documentation/platform/kms/hsm-integration.mdx index 45d883977..7a8d15fe5 100644 --- a/docs/documentation/platform/kms/hsm-integration.mdx +++ b/docs/documentation/platform/kms/hsm-integration.mdx @@ -1428,7 +1428,7 @@ Enabling HSM encryption has a set of key benefits: infisical: image: repository: infisical/infisical - tag: "v0.151.0-nightly-20251013.1" + tag: "v0.151.0" pullPolicy: IfNotPresent extraVolumeMounts: diff --git a/docs/images/app-connections/github/create-pat-form.png b/docs/images/app-connections/github/create-pat-form.png new file mode 100644 index 000000000..cc8abdf15 Binary files /dev/null and b/docs/images/app-connections/github/create-pat-form.png differ diff --git a/docs/images/app-connections/github/create-pat-method.png b/docs/images/app-connections/github/create-pat-method.png new file mode 100644 index 000000000..eec811705 Binary files /dev/null and b/docs/images/app-connections/github/create-pat-method.png differ diff --git a/docs/images/app-connections/github/create-pat-token-generated.png b/docs/images/app-connections/github/create-pat-token-generated.png new file mode 100644 index 000000000..430e67671 Binary files /dev/null and b/docs/images/app-connections/github/create-pat-token-generated.png differ diff --git a/docs/images/app-connections/github/pat-connection.png b/docs/images/app-connections/github/pat-connection.png new file mode 100644 index 000000000..802ed4e38 Binary files /dev/null and b/docs/images/app-connections/github/pat-connection.png differ diff --git a/docs/images/integrations/github/integrations-github-config-new-pat.png b/docs/images/integrations/github/integrations-github-config-new-pat.png new file mode 100644 index 000000000..11766edfd Binary files /dev/null and b/docs/images/integrations/github/integrations-github-config-new-pat.png differ diff --git a/docs/images/platform/gateways/assign-project.png b/docs/images/platform/gateways/assign-project.png deleted file mode 100644 index a1ff61909..000000000 Binary files a/docs/images/platform/gateways/assign-project.png and /dev/null differ diff --git a/docs/images/platform/gateways/create-identity-for-gateway.png b/docs/images/platform/gateways/create-identity-for-gateway.png deleted file mode 100644 index d7ef6b02a..000000000 Binary files a/docs/images/platform/gateways/create-identity-for-gateway.png and /dev/null differ diff --git a/docs/images/platform/gateways/dynamic-secret.png b/docs/images/platform/gateways/dynamic-secret.png deleted file mode 100644 index bf742413e..000000000 Binary files a/docs/images/platform/gateways/dynamic-secret.png and /dev/null differ diff --git a/docs/images/platform/gateways/edit-gateway.png b/docs/images/platform/gateways/edit-gateway.png deleted file mode 100644 index 04ef2a7d2..000000000 Binary files a/docs/images/platform/gateways/edit-gateway.png and /dev/null differ diff --git a/docs/images/platform/gateways/gateway-list.png b/docs/images/platform/gateways/gateway-list.png deleted file mode 100644 index 11f8206fe..000000000 Binary files a/docs/images/platform/gateways/gateway-list.png and /dev/null differ diff --git a/docs/integrations/app-connections/github.mdx b/docs/integrations/app-connections/github.mdx index e44fc405a..40eb0525f 100644 --- a/docs/integrations/app-connections/github.mdx +++ b/docs/integrations/app-connections/github.mdx @@ -3,7 +3,7 @@ title: "GitHub Connection" description: "Learn how to configure a GitHub Connection for Infisical." --- -Infisical supports two methods for connecting to GitHub. +Infisical supports three methods for connecting to GitHub. @@ -178,5 +178,72 @@ Infisical supports two methods for connecting to GitHub. + + Infisical will use a Personal Access Token to connect to GitHub. + + ## Create a Personal Access Token + + + Navigate to your user Settings > Developer settings > Personal Access Tokens to create a new Personal Access Token. + + ![integrations github config](../../images/integrations/github/integrations-github-config-settings.png) + ![integrations github config](../../images/integrations/github/integrations-github-config-dev-settings.png) + ![integrations github config](../../images/integrations/github/integrations-github-config-new-pat.png) + + Click **Generate new token** to create the token. + + + Fill in the Personal Access Token details: + - **Token name:** A descriptive name for the token (e.g., "infisical-connection-token") + - **Repository access:** Select the repositories you want to grant access to + - Select `All repositories` or `Only selected repositories` to be able to manage the secrets in the selected repositories. + - **Select scopes:** Add the following scopes: + - **Metadata**: Read-only + - **Environments**: Read and write + - **Secrets**: Read and write + + ![Personal Access Token Form](/images/app-connections/github/create-pat-form.png) + + Click **Generate token** to create the token. + + + Copy the generated token immediately as it won't be shown again. + + ![Personal Access Token Generated](/images/app-connections/github/create-pat-token-generated.png) + + + Keep your Personal Access Token secure and do not share it. Anyone with access to this token can access your GitHub account and repositories. + + + + + ## Setup GitHub Connection in Infisical + + + + Navigate to the **App Connections** page in the desired project. + ![App Connections Tab](/images/app-connections/general/add-connection.png) + + + Select the **GitHub Connection** option from the connection options modal. + ![Select GitHub Connection](/images/app-connections/github/select-github-connection.png) + + + Select the **Personal Access Token** method and fill in the **Personal Access Token** field with your Personal Access Token. + + You may optionally configure GitHub Enterprise options: + - **Gateway:** The gateway connected to your private network + - **Hostname:** The hostname at which to access your GitHub Enterprise instance + + Click **Create Connection**. + + ![Personal Access Token](/images/app-connections/github/create-pat-method.png) + + + Your **GitHub Connection** is now available for use. + ![Personal Access Token Connection](/images/app-connections/github/pat-connection.png) + + + diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index 551c79184..edebf1670 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -116,6 +116,27 @@ The platform utilizes Postgres to persist all of its data and Redis for caching Configure the SSL certificate for securing a Postgres connection by first encoding it in base64. Use the following command to encode your certificate: `echo "" | base64` + + Many cloud providers provide a CA certificate for their data regions that you can use to secure your connection with SSL. + + + + If you're hosting your database on AWS RDS, you can use their publicly available CA certificate as the database root certificate. + + You can find all the available CA certificates for AWS RDS on the official [AWS RDS documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html). + + As an example, if your RDS cluster is hosted in `us-east-1` _(US East, N. Virginia)_, you can use the following root certificate: https://truststore.pki.rds.amazonaws.com/us-east-1/us-east-1-bundle.pem. + + All the available CA certificates can be found in the AWS RDS documentation linked above. + + Remember to base64 encode the certificate before setting it as the `DB_ROOT_CERT` environment variable. `cat /path/to/certificate.pem | base64`. + + ```bash + DB_ROOT_CERT=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1 # .... (base64 encoded certificate) + DB_CONNECTION_URI=?sslmode=verify-ca # or verify-full depending on your security policies + ``` + + diff --git a/docs/self-hosting/deployment-options/kubernetes-helm.mdx b/docs/self-hosting/deployment-options/kubernetes-helm.mdx index f8a2a6864..f4ef67e1e 100644 --- a/docs/self-hosting/deployment-options/kubernetes-helm.mdx +++ b/docs/self-hosting/deployment-options/kubernetes-helm.mdx @@ -1,8 +1,10 @@ --- title: "Kubernetes via Helm Chart" description: "Learn how to use Helm chart to install Infisical on your Kubernetes cluster." ---- +--- + **Prerequisites** + - You have extensive understanding of [Kubernetes](https://kubernetes.io/) - Installed [Helm package manager](https://helm.sh/) version v3.11.3 or greater - You have [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) installed and connected to your kubernetes cluster @@ -12,7 +14,7 @@ description: "Learn how to use Helm chart to install Infisical on your Kubernete ```bash helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/' ``` - ``` + ```bash helm repo update ``` @@ -61,6 +63,7 @@ description: "Learn how to use Helm chart to install Infisical on your Kubernete For production environments, we recommend using Cloud-based Platform as a Service (PaaS) solutions for PostgreSQL and Redis to ensure high availability. In on-premise setups, it's recommended to configure Redis and Postgres for high availability, either by using Bitnami charts or a custom configuration. + ```yaml simple-values-example.yaml apiVersion: v1 kind: Secret @@ -74,6 +77,10 @@ description: "Learn how to use Helm chart to install Infisical on your Kubernete DB_CONNECTION_URI: <> SITE_URL: <> ``` + + + If you need to configure the SSL certificate for your production Postgres instance, you can use the `DB_ROOT_CERT` environment variable. [Learn more about configuring the SSL certificate](/self-hosting/configuration/envars#aws-rds). + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6e20b6d35..e8fdda096 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -138,10 +138,10 @@ "tailwindcss": "^4.1.14", "typescript": "~5.6.2", "typescript-eslint": "^8.15.0", - "vite": "^5.4.18", - "vite-plugin-node-polyfills": "^0.22.0", + "vite": "^6.2.0", + "vite-plugin-node-polyfills": "^0.23.0", "vite-plugin-top-level-await": "^1.4.4", - "vite-plugin-wasm": "^3.3.0", + "vite-plugin-wasm": "^3.4.0", "vite-tsconfig-paths": "^5.1.4" } }, @@ -717,9 +717,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", "cpu": [ "ppc64" ], @@ -730,13 +730,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", "cpu": [ "arm" ], @@ -747,13 +747,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", "cpu": [ "arm64" ], @@ -764,13 +764,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", "cpu": [ "x64" ], @@ -781,13 +781,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", "cpu": [ "arm64" ], @@ -798,13 +798,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", "cpu": [ "x64" ], @@ -815,13 +815,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", "cpu": [ "arm64" ], @@ -832,13 +832,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", "cpu": [ "x64" ], @@ -849,13 +849,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", "cpu": [ "arm" ], @@ -866,13 +866,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", "cpu": [ "arm64" ], @@ -883,13 +883,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", "cpu": [ "ia32" ], @@ -900,13 +900,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", "cpu": [ "loong64" ], @@ -917,13 +917,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", "cpu": [ "mips64el" ], @@ -934,13 +934,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", "cpu": [ "ppc64" ], @@ -951,13 +951,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", "cpu": [ "riscv64" ], @@ -968,13 +968,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", "cpu": [ "s390x" ], @@ -985,13 +985,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", "cpu": [ "x64" ], @@ -1002,13 +1002,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", - "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", "cpu": [ "arm64" ], @@ -1023,9 +1023,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", "cpu": [ "x64" ], @@ -1036,13 +1036,13 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", - "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", "cpu": [ "arm64" ], @@ -1057,9 +1057,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", "cpu": [ "x64" ], @@ -1070,13 +1070,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", "cpu": [ "x64" ], @@ -1087,13 +1104,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", "cpu": [ "arm64" ], @@ -1104,13 +1121,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", "cpu": [ "ia32" ], @@ -1121,13 +1138,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", "cpu": [ "x64" ], @@ -1138,7 +1155,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -3725,9 +3742,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", - "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", "cpu": [ "arm" ], @@ -3739,9 +3756,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", - "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", "cpu": [ "arm64" ], @@ -3753,9 +3770,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", - "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", "cpu": [ "arm64" ], @@ -3767,9 +3784,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", - "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", "cpu": [ "x64" ], @@ -3781,9 +3798,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", - "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", "cpu": [ "arm64" ], @@ -3795,9 +3812,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", - "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", "cpu": [ "x64" ], @@ -3809,9 +3826,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", - "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", "cpu": [ "arm" ], @@ -3823,9 +3840,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", - "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", "cpu": [ "arm" ], @@ -3837,9 +3854,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", - "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", "cpu": [ "arm64" ], @@ -3851,9 +3868,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", - "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", "cpu": [ "arm64" ], @@ -3864,10 +3881,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", - "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", "cpu": [ "loong64" ], @@ -3878,10 +3895,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", - "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", "cpu": [ "ppc64" ], @@ -3893,9 +3910,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", - "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", "cpu": [ "riscv64" ], @@ -3907,9 +3938,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", - "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", "cpu": [ "s390x" ], @@ -3921,9 +3952,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", - "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", "cpu": [ "x64" ], @@ -3935,9 +3966,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", - "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", "cpu": [ "x64" ], @@ -3948,10 +3979,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", - "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", "cpu": [ "arm64" ], @@ -3963,9 +4008,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", - "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", "cpu": [ "ia32" ], @@ -3976,10 +4021,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", - "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", "cpu": [ "x64" ], @@ -5406,9 +5465,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -8148,9 +8207,9 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -8158,32 +8217,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" } }, "node_modules/esbuild-register": { @@ -9015,6 +9077,24 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -11926,9 +12006,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -12498,9 +12578,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { "node": ">=12" @@ -12541,9 +12621,9 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -12561,7 +12641,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -13638,13 +13718,13 @@ } }, "node_modules/rollup": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", - "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -13654,25 +13734,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.28.1", - "@rollup/rollup-android-arm64": "4.28.1", - "@rollup/rollup-darwin-arm64": "4.28.1", - "@rollup/rollup-darwin-x64": "4.28.1", - "@rollup/rollup-freebsd-arm64": "4.28.1", - "@rollup/rollup-freebsd-x64": "4.28.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", - "@rollup/rollup-linux-arm-musleabihf": "4.28.1", - "@rollup/rollup-linux-arm64-gnu": "4.28.1", - "@rollup/rollup-linux-arm64-musl": "4.28.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", - "@rollup/rollup-linux-riscv64-gnu": "4.28.1", - "@rollup/rollup-linux-s390x-gnu": "4.28.1", - "@rollup/rollup-linux-x64-gnu": "4.28.1", - "@rollup/rollup-linux-x64-musl": "4.28.1", - "@rollup/rollup-win32-arm64-msvc": "4.28.1", - "@rollup/rollup-win32-ia32-msvc": "4.28.1", - "@rollup/rollup-win32-x64-msvc": "4.28.1", + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" } }, @@ -14496,6 +14579,23 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/tinyrainbow": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", @@ -14652,438 +14752,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", - "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", - "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", - "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", - "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", - "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", - "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", - "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", - "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", - "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", - "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", - "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", - "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", - "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", - "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", - "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", - "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", - "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", - "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", - "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", - "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", - "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", - "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", - "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/esbuild": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", - "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.1", - "@esbuild/android-arm": "0.25.1", - "@esbuild/android-arm64": "0.25.1", - "@esbuild/android-x64": "0.25.1", - "@esbuild/darwin-arm64": "0.25.1", - "@esbuild/darwin-x64": "0.25.1", - "@esbuild/freebsd-arm64": "0.25.1", - "@esbuild/freebsd-x64": "0.25.1", - "@esbuild/linux-arm": "0.25.1", - "@esbuild/linux-arm64": "0.25.1", - "@esbuild/linux-ia32": "0.25.1", - "@esbuild/linux-loong64": "0.25.1", - "@esbuild/linux-mips64el": "0.25.1", - "@esbuild/linux-ppc64": "0.25.1", - "@esbuild/linux-riscv64": "0.25.1", - "@esbuild/linux-s390x": "0.25.1", - "@esbuild/linux-x64": "0.25.1", - "@esbuild/netbsd-arm64": "0.25.1", - "@esbuild/netbsd-x64": "0.25.1", - "@esbuild/openbsd-arm64": "0.25.1", - "@esbuild/openbsd-x64": "0.25.1", - "@esbuild/sunos-x64": "0.25.1", - "@esbuild/win32-arm64": "0.25.1", - "@esbuild/win32-ia32": "0.25.1", - "@esbuild/win32-x64": "0.25.1" - } - }, "node_modules/tsyringe": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.8.0.tgz", @@ -15610,21 +15278,24 @@ } }, "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -15633,19 +15304,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -15666,13 +15343,19 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, "node_modules/vite-plugin-node-polyfills": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.22.0.tgz", - "integrity": "sha512-F+G3LjiGbG8QpbH9bZ//GSBr9i1InSTkaulfUHFa9jkLqVGORFBoqc2A/Yu5Mmh1kNAbiAeKeK+6aaQUf3x0JA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.23.0.tgz", + "integrity": "sha512-4n+Ys+2bKHQohPBKigFlndwWQ5fFKwaGY6muNDMTb0fSQLyBzS+jjUNRZG9sKF0S/Go4ApG6LFnUGopjkILg3w==", "dev": true, "license": "MIT", "dependencies": { @@ -15683,7 +15366,7 @@ "url": "https://github.com/sponsors/davidmyersdev" }, "peerDependencies": { - "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "node_modules/vite-plugin-top-level-await": { @@ -15702,13 +15385,13 @@ } }, "node_modules/vite-plugin-wasm": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.3.0.tgz", - "integrity": "sha512-tVhz6w+W9MVsOCHzxo6SSMSswCeIw4HTrXEi6qL3IRzATl83jl09JVO1djBqPSwfjgnpVHNLYcaMbaDX5WB/pg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.5.0.tgz", + "integrity": "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ==", "dev": true, "license": "MIT", "peerDependencies": { - "vite": "^2 || ^3 || ^4 || ^5" + "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7" } }, "node_modules/vite-tsconfig-paths": { diff --git a/frontend/package.json b/frontend/package.json index 9c375190e..8009c2118 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -147,10 +147,10 @@ "tailwindcss": "^4.1.14", "typescript": "~5.6.2", "typescript-eslint": "^8.15.0", - "vite": "^5.4.18", - "vite-plugin-node-polyfills": "^0.22.0", + "vite": "^6.2.0", + "vite-plugin-node-polyfills": "^0.23.0", "vite-plugin-top-level-await": "^1.4.4", - "vite-plugin-wasm": "^3.3.0", + "vite-plugin-wasm": "^3.4.0", "vite-tsconfig-paths": "^5.1.4" } } diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index bdb26c796..9ee03c3c0 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -135,6 +135,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) case GitHubConnectionMethod.App: case GitHubRadarConnectionMethod.App: return { name: "GitHub App", icon: faGithub }; + case GitHubConnectionMethod.Pat: + return { name: "Personal Access Token", icon: faKey }; case AzureKeyVaultConnectionMethod.OAuth: case AzureAppConfigurationConnectionMethod.OAuth: case AzureClientSecretsConnectionMethod.OAuth: diff --git a/frontend/src/hooks/api/appConnections/types/github-connection.ts b/frontend/src/hooks/api/appConnections/types/github-connection.ts index 27bed2dcb..595e5adbb 100644 --- a/frontend/src/hooks/api/appConnections/types/github-connection.ts +++ b/frontend/src/hooks/api/appConnections/types/github-connection.ts @@ -3,7 +3,8 @@ import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-con export enum GitHubConnectionMethod { App = "github-app", - OAuth = "oauth" + OAuth = "oauth", + Pat = "pat" } export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub } & ( @@ -24,4 +25,12 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub host?: string; }; } + | { + method: GitHubConnectionMethod.Pat; + credentials: { + personalAccessToken: string; + instanceType?: "cloud" | "server"; + host?: string; + }; + } ); diff --git a/frontend/src/hooks/api/certificateProfiles/types.ts b/frontend/src/hooks/api/certificateProfiles/types.ts index a9b6b7060..b5c53e11b 100644 --- a/frontend/src/hooks/api/certificateProfiles/types.ts +++ b/frontend/src/hooks/api/certificateProfiles/types.ts @@ -35,7 +35,7 @@ export type TCertificateProfileWithDetails = TCertificateProfile & { apiConfig?: { id: string; autoRenew: boolean; - autoRenewDays?: number; + renewBeforeDays?: number; }; }; @@ -53,7 +53,7 @@ export type TCreateCertificateProfileDTO = { }; apiConfig?: { autoRenew?: boolean; - autoRenewDays?: number; + renewBeforeDays?: number; }; }; @@ -68,7 +68,7 @@ export type TUpdateCertificateProfileDTO = { }; apiConfig?: { autoRenew?: boolean; - autoRenewDays?: number; + renewBeforeDays?: number; }; }; diff --git a/frontend/src/hooks/api/certificates/index.tsx b/frontend/src/hooks/api/certificates/index.tsx index ddac04730..a60ebf91e 100644 --- a/frontend/src/hooks/api/certificates/index.tsx +++ b/frontend/src/hooks/api/certificates/index.tsx @@ -1,2 +1,8 @@ -export { useDeleteCert, useImportCertificate, useRevokeCert } from "./mutations"; +export { + useDeleteCert, + useImportCertificate, + useRenewCertificate, + useRevokeCert, + useUpdateRenewalConfig +} from "./mutations"; export { useGetCert, useGetCertBody } from "./queries"; diff --git a/frontend/src/hooks/api/certificates/mutations.tsx b/frontend/src/hooks/api/certificates/mutations.tsx index 388295b0a..eed1d9e5f 100644 --- a/frontend/src/hooks/api/certificates/mutations.tsx +++ b/frontend/src/hooks/api/certificates/mutations.tsx @@ -9,7 +9,10 @@ import { TDeleteCertDTO, TImportCertificateDTO, TImportCertificateResponse, - TRevokeCertDTO + TRenewCertificateDTO, + TRenewCertificateResponse, + TRevokeCertDTO, + TUpdateRenewalConfigDTO } from "./types"; export const useDeleteCert = () => { @@ -77,3 +80,57 @@ export const useImportCertificate = () => { } }); }; + +export const useRenewCertificate = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ certificateId }) => { + const { data } = await apiRequest.post( + `/api/v3/certificates/${certificateId}/renew`, + {} + ); + return data; + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: ["certificate-profiles", "list"] + }); + queryClient.invalidateQueries({ + queryKey: pkiSubscriberKeys.allPkiSubscriberCertificates() + }); + queryClient.invalidateQueries({ + queryKey: projectKeys.allProjectCertificates() + }); + if (data.projectId) { + queryClient.invalidateQueries({ + queryKey: projectKeys.forProjectCertificates(data.projectId) + }); + } + } + }); +}; + +export const useUpdateRenewalConfig = () => { + const queryClient = useQueryClient(); + return useMutation< + { message: string; renewBeforeDays?: number }, + object, + TUpdateRenewalConfigDTO + >({ + mutationFn: async ({ certificateId, renewBeforeDays, enableAutoRenewal }) => { + const { data } = await apiRequest.patch<{ message: string; renewBeforeDays?: number }>( + `/api/v3/certificates/${certificateId}/config`, + { renewBeforeDays, enableAutoRenewal } + ); + return data; + }, + onSuccess: (_, { projectSlug }) => { + queryClient.invalidateQueries({ + queryKey: projectKeys.forProjectCertificates(projectSlug) + }); + queryClient.invalidateQueries({ + queryKey: projectKeys.allProjectCertificates() + }); + } + }); +}; diff --git a/frontend/src/hooks/api/certificates/types.ts b/frontend/src/hooks/api/certificates/types.ts index 1ec3292a3..622276e24 100644 --- a/frontend/src/hooks/api/certificates/types.ts +++ b/frontend/src/hooks/api/certificates/types.ts @@ -4,6 +4,7 @@ export type TCertificate = { id: string; caId: string; certificateTemplateId?: string; + profileId?: string; status: CertStatus; friendlyName: string; commonName: string; @@ -13,6 +14,12 @@ export type TCertificate = { notAfter: string; keyUsages: CertKeyUsage[]; extendedKeyUsages: CertExtendedKeyUsage[]; + renewBeforeDays?: number; + renewedBy?: string; + renewedFromCertificateId?: string; + renewedByCertificateId?: string; + renewalError?: string; + hasPrivateKey?: boolean; }; export type TDeleteCertDTO = { @@ -43,3 +50,24 @@ export type TImportCertificateResponse = { privateKey: string; serialNumber: string; }; + +export type TRenewCertificateDTO = { + certificateId: string; +}; + +export type TRenewCertificateResponse = { + certificate: string; + issuingCaCertificate: string; + certificateChain: string; + privateKey?: string; + serialNumber: string; + certificateId: string; + projectId: string; +}; + +export type TUpdateRenewalConfigDTO = { + certificateId: string; + renewBeforeDays?: number; + enableAutoRenewal?: boolean; + projectSlug: string; +}; diff --git a/frontend/src/hooks/api/pam/enums.ts b/frontend/src/hooks/api/pam/enums.ts index b6e5ce64c..0684f6073 100644 --- a/frontend/src/hooks/api/pam/enums.ts +++ b/frontend/src/hooks/api/pam/enums.ts @@ -1,5 +1,6 @@ export enum PamResourceType { Postgres = "postgres", + MySQL = "mysql", RDP = "rdp", SSH = "ssh", Kubernetes = "kubernetes" diff --git a/frontend/src/hooks/api/pam/maps.ts b/frontend/src/hooks/api/pam/maps.ts index a27507d7d..c240a12ad 100644 --- a/frontend/src/hooks/api/pam/maps.ts +++ b/frontend/src/hooks/api/pam/maps.ts @@ -5,6 +5,7 @@ export const PAM_RESOURCE_TYPE_MAP: Record< { name: string; image: string; size?: number } > = { [PamResourceType.Postgres]: { name: "PostgreSQL", image: "Postgres.png" }, + [PamResourceType.MySQL]: { name: "MySQL", image: "MySql.png" }, [PamResourceType.RDP]: { name: "RDP", image: "RDP.png" }, [PamResourceType.SSH]: { name: "SSH", image: "SSH.png" }, [PamResourceType.Kubernetes]: { name: "Kubernetes", image: "Kubernetes.png" } diff --git a/frontend/src/hooks/api/pam/types/index.ts b/frontend/src/hooks/api/pam/types/index.ts index 1b4acf6d4..23cdf389d 100644 --- a/frontend/src/hooks/api/pam/types/index.ts +++ b/frontend/src/hooks/api/pam/types/index.ts @@ -1,11 +1,13 @@ import { PamResourceType, PamSessionStatus } from "../enums"; import { TPostgresAccount, TPostgresResource } from "./postgres-resource"; +import { TMySQLAccount, TMySQLResource } from "./mysql-resource"; export * from "./postgres-resource"; +export * from "./mysql-resource"; -export type TPamResource = TPostgresResource; +export type TPamResource = TPostgresResource | TMySQLResource; -export type TPamAccount = TPostgresAccount; +export type TPamAccount = TPostgresAccount | TMySQLAccount; export type TPamFolder = { id: string; diff --git a/frontend/src/hooks/api/pam/types/mysql-resource.ts b/frontend/src/hooks/api/pam/types/mysql-resource.ts new file mode 100644 index 000000000..f1a7c09c8 --- /dev/null +++ b/frontend/src/hooks/api/pam/types/mysql-resource.ts @@ -0,0 +1,14 @@ +import { PamResourceType } from "../enums"; +import { TBaseSqlConnectionDetails, TBaseSqlCredentials } from "./shared/sql-resource"; +import { TBasePamAccount } from "./base-account"; +import { TBasePamResource } from "./base-resource"; + +// Resources +export type TMySQLResource = TBasePamResource & { resourceType: PamResourceType.MySQL } & { + connectionDetails: TBaseSqlConnectionDetails; +}; + +// Accounts +export type TMySQLAccount = TBasePamAccount & { + credentials: TBaseSqlCredentials; +}; diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx new file mode 100644 index 000000000..d6199678a --- /dev/null +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx @@ -0,0 +1,265 @@ +import { useEffect, useMemo } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; +import { useProject } from "@app/context"; +import { useUpdateRenewalConfig } from "@app/hooks/api"; +import { useGetCertificateProfileById } from "@app/hooks/api/certificateProfiles"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +const DEFAULT_RENEWAL_BEFORE_DAYS = 20; +const MIN_RENEWAL_BEFORE_DAYS = 1; +const MAX_RENEWAL_BEFORE_DAYS = 30; + +const createFormSchema = (ttlDays: number, notAfter: string) => + z.object({ + renewBeforeDays: z + .number() + .min(MIN_RENEWAL_BEFORE_DAYS, `Renewal days must be at least ${MIN_RENEWAL_BEFORE_DAYS}`) + .max(MAX_RENEWAL_BEFORE_DAYS, `Renewal days cannot exceed ${MAX_RENEWAL_BEFORE_DAYS}`) + .refine( + (value) => value < ttlDays, + (value) => ({ + message: `Renewal days (${value}) must be less than certificate TTL (${ttlDays} days)` + }) + ) + .refine( + (value) => { + const expiryDate = new Date(notAfter); + const renewalDate = new Date(expiryDate.getTime() - value * 24 * 60 * 60 * 1000); + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + tomorrow.setHours(0, 0, 0, 0); + return renewalDate >= tomorrow; + }, + () => ({ + message: "Renewals can only be scheduled from tomorrow onwards." + }) + ) + }); + +type FormData = z.infer>; + +type Props = { + popUp: UsePopUpState<["manageRenewal"]>; + handlePopUpToggle: (popUpName: keyof UsePopUpState<["manageRenewal"]>, state?: boolean) => void; +}; + +const RenewalConfigForm = ({ + control, + errors, + onSubmit, + isLoading, + buttonText, + onCancel +}: { + control: any; + errors: { renewBeforeDays?: { message?: string } }; + onSubmit: (e?: React.BaseSyntheticEvent) => Promise; + isLoading: boolean; + buttonText: string; + onCancel: () => void; +}) => ( +
+ + ( + { + const value = parseInt(e.target.value, 10); + field.onChange(value); + }} + placeholder="Enter days before expiration" + /> + )} + /> + + +
+ + +
+
+); + +export const CertificateManageRenewalModal = ({ popUp, handlePopUpToggle }: Props) => { + const { currentProject } = useProject(); + const { mutateAsync: updateRenewalConfig, isPending: isUpdatingConfig } = + useUpdateRenewalConfig(); + + const certificateData = popUp.manageRenewal.data as { + certificateId: string; + commonName: string; + profileId: string; + renewBeforeDays?: number; + ttlDays?: number; + notAfter: string; + renewalError?: string; + renewedFromCertificateId?: string; + renewedByCertificateId?: string; + }; + + const { data: profileData } = useGetCertificateProfileById({ + profileId: certificateData?.profileId || "" + }); + + const defaultRenewalDays = useMemo(() => { + if (certificateData?.renewBeforeDays) { + return certificateData.renewBeforeDays; + } + if (profileData?.apiConfig?.renewBeforeDays) { + return profileData.apiConfig.renewBeforeDays; + } + return DEFAULT_RENEWAL_BEFORE_DAYS; + }, [certificateData?.renewBeforeDays, profileData?.apiConfig?.renewBeforeDays]); + + const isAutoRenewalEnabled = Boolean( + certificateData?.renewBeforeDays && certificateData.renewBeforeDays > 0 + ); + + const hasRenewalError = Boolean(certificateData?.renewalError); + + const formSchema = createFormSchema( + certificateData?.ttlDays || 365, + certificateData?.notAfter || "" + ); + + const { + control, + handleSubmit, + formState: { errors }, + reset + } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + renewBeforeDays: defaultRenewalDays + } + }); + + useEffect(() => { + if (popUp.manageRenewal.isOpen) { + reset({ + renewBeforeDays: defaultRenewalDays + }); + } + }, [popUp.manageRenewal.isOpen, defaultRenewalDays, reset]); + + const onUpdateRenewal = async (data: FormData) => { + try { + if (!currentProject?.slug) { + createNotification({ + text: "Unable to update auto-renewal: Project not found. Please refresh the page and try again.", + type: "error" + }); + return; + } + + await updateRenewalConfig({ + certificateId: certificateData.certificateId, + renewBeforeDays: data.renewBeforeDays, + projectSlug: currentProject.slug + }); + + createNotification({ + text: isAutoRenewalEnabled + ? "Auto-renewal configuration updated successfully" + : "Auto-renewal enabled successfully", + type: "success" + }); + + handlePopUpToggle("manageRenewal", false); + } catch (err) { + console.error(err); + createNotification({ + text: isAutoRenewalEnabled + ? "Failed to update auto-renewal configuration. Please check your inputs and try again." + : "Failed to enable auto-renewal. Please check your inputs and try again.", + type: "error" + }); + } + }; + + const getModalTitle = () => { + if (hasRenewalError) { + return `Fix Auto-Renewal: ${certificateData?.commonName || ""}`; + } + if (isAutoRenewalEnabled) { + return `Manage Auto-Renewal for ${certificateData?.commonName || ""}`; + } + return `Enable Auto-Renewal for ${certificateData?.commonName || ""}`; + }; + + if (!certificateData) { + return null; + } + + return ( + { + handlePopUpToggle("manageRenewal", isOpen); + }} + > + + {hasRenewalError && ( +
+
+
+ ! +
+
+

Automatic Renewal Failed

+

+ The last automatic renewal attempt failed: {certificateData.renewalError} +

+

+ You can reconfigure auto-renewal below or disable it completely. +

+
+
+
+ )} + + {(!isAutoRenewalEnabled || hasRenewalError) && ( + handlePopUpToggle("manageRenewal", false)} + /> + )} + + {isAutoRenewalEnabled && !hasRenewalError && ( + handlePopUpToggle("manageRenewal", false)} + /> + )} +
+
+ ); +}; diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalConfigModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalConfigModal.tsx new file mode 100644 index 000000000..c952f1e54 --- /dev/null +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalConfigModal.tsx @@ -0,0 +1,178 @@ +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; +import { useProject } from "@app/context"; +import { useUpdateRenewalConfig } from "@app/hooks/api"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +const createFormSchema = (ttlDays: number) => + z.object({ + renewBeforeDays: z + .number() + .min(1, "Renewal days must be at least 1") + .max(365, "Renewal days cannot exceed 365") + .refine( + (value) => value < ttlDays, + (value) => ({ + message: `Renewal days (${value}) must be less than certificate TTL (${ttlDays} days)` + }) + ) + }); + +type FormData = z.infer>; + +type Props = { + popUp: UsePopUpState<["configureRenewal"]>; + handlePopUpToggle: ( + popUpName: keyof UsePopUpState<["configureRenewal"]>, + state?: boolean + ) => void; +}; + +export const CertificateRenewalConfigModal = ({ popUp, handlePopUpToggle }: Props) => { + const { currentProject } = useProject(); + const { mutateAsync: updateRenewalConfig, isPending: isSubmitting } = useUpdateRenewalConfig(); + + const certificateData = popUp.configureRenewal.data as { + certificateId: string; + commonName: string; + profileId: string; + renewBeforeDays?: number; + ttlDays: number; + }; + + const formSchema = createFormSchema(certificateData.ttlDays); + + const { + control, + handleSubmit, + formState: { errors }, + watch + } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + renewBeforeDays: certificateData?.renewBeforeDays || 1 + } + }); + + const renewBeforeDays = watch("renewBeforeDays"); + + const onSubmit = async (data: FormData) => { + try { + if (!currentProject?.slug) { + createNotification({ + text: "Project not found", + type: "error" + }); + return; + } + + await updateRenewalConfig({ + certificateId: certificateData.certificateId, + renewBeforeDays: data.renewBeforeDays, + projectSlug: currentProject.slug + }); + + createNotification({ + text: "Successfully updated auto-renewal configuration", + type: "success" + }); + + handlePopUpToggle("configureRenewal", false); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to update auto-renewal configuration", + type: "error" + }); + } + }; + + return ( + { + handlePopUpToggle("configureRenewal", isOpen); + }} + > + +
+
+

+ Configure when this certificate should be automatically renewed. The certificate will + be renewed when it has the specified number of days remaining before expiration. +

+ +
+

+ Certificate TTL: {certificateData?.ttlDays} days +

+

+ Current Setting:{" "} + {certificateData?.renewBeforeDays + ? `${certificateData.renewBeforeDays} days before expiration` + : "Disabled"} +

+
+ + ( + + { + const value = parseInt(e.target.value, 10); + field.onChange(Number.isNaN(value) ? 0 : value); + }} + /> + + )} + /> + + {renewBeforeDays && certificateData?.ttlDays && ( +
+

+ {renewBeforeDays >= certificateData.ttlDays + ? "⚠️ Renewal days must be less than certificate TTL" + : `✓ Certificate will be renewed ${renewBeforeDays} days before expiration`} +

+
+ )} +
+ +
+ + +
+
+
+
+ ); +}; diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalDisableModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalDisableModal.tsx new file mode 100644 index 000000000..613080cd7 --- /dev/null +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalDisableModal.tsx @@ -0,0 +1,94 @@ +import { createNotification } from "@app/components/notifications"; +import { Button, Modal, ModalContent } from "@app/components/v2"; +import { useProject } from "@app/context"; +import { useUpdateRenewalConfig } from "@app/hooks/api"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +type Props = { + popUp: UsePopUpState<["disableRenewal"]>; + handlePopUpToggle: (popUpName: keyof UsePopUpState<["disableRenewal"]>, state?: boolean) => void; +}; + +export const CertificateRenewalDisableModal = ({ popUp, handlePopUpToggle }: Props) => { + const { currentProject } = useProject(); + const { mutateAsync: updateRenewalConfig, isPending: isSubmitting } = useUpdateRenewalConfig(); + + const certificateData = popUp.disableRenewal.data as { + certificateId: string; + commonName: string; + }; + + const onDisableConfirm = async () => { + try { + if (!currentProject?.slug) { + createNotification({ + text: "Project not found", + type: "error" + }); + return; + } + + await updateRenewalConfig({ + certificateId: certificateData.certificateId, + projectSlug: currentProject.slug, + enableAutoRenewal: false + }); + + createNotification({ + text: "Successfully disabled auto-renewal", + type: "success" + }); + + handlePopUpToggle("disableRenewal", false); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to disable auto-renewal", + type: "error" + }); + } + }; + + return ( + { + handlePopUpToggle("disableRenewal", isOpen); + }} + > + +
+

+ Are you sure you want to disable auto-renewal for this certificate? +

+
+

+ Warning: Once disabled, this certificate will not be automatically + renewed and may expire without notice. You can re-enable auto-renewal at any time. +

+
+
+ +
+ + +
+
+
+ ); +}; diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalModal.tsx new file mode 100644 index 000000000..0e2b1c17d --- /dev/null +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalModal.tsx @@ -0,0 +1,80 @@ +import { faRedo } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { createNotification } from "@app/components/notifications"; +import { Button, Modal, ModalContent } from "@app/components/v2"; +import { useRenewCertificate } from "@app/hooks/api"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +type Props = { + popUp: UsePopUpState<["renewCertificate"]>; + handlePopUpToggle: ( + popUpName: keyof UsePopUpState<["renewCertificate"]>, + state?: boolean + ) => void; +}; + +export const CertificateRenewalModal = ({ popUp, handlePopUpToggle }: Props) => { + const { mutateAsync: renewCertificate, isPending: isRenewing } = useRenewCertificate(); + + const onRenewConfirm = async () => { + try { + const { certificateId } = popUp.renewCertificate.data as { certificateId: string }; + + await renewCertificate({ + certificateId + }); + + createNotification({ + text: "Certificate renewed successfully", + type: "success" + }); + + handlePopUpToggle("renewCertificate", false); + } catch (err) { + console.error(err); + } + }; + + const certificateData = popUp.renewCertificate.data as { + certificateId: string; + commonName: string; + profileId: string; + }; + + return ( + { + handlePopUpToggle("renewCertificate", isOpen); + }} + > + +
+

+ Are you sure you want to renew this certificate now? +

+
+ +
+ + +
+
+
+ ); +}; diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx index bce21123b..4102d8ea5 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx @@ -16,7 +16,9 @@ import { usePopUp } from "@app/hooks/usePopUp"; import { CertificateCertModal } from "./CertificateCertModal"; import { CertificateImportModal } from "./CertificateImportModal"; import { CertificateIssuanceModal } from "./CertificateIssuanceModal"; +import { CertificateManageRenewalModal } from "./CertificateManageRenewalModal"; import { CertificateModal } from "./CertificateModal"; +import { CertificateRenewalModal } from "./CertificateRenewalModal"; import { CertificateRevocationModal } from "./CertificateRevocationModal"; import { CertificatesTable } from "./CertificatesTable"; @@ -33,7 +35,9 @@ export const CertificatesSection = () => { "certificateImport", "certificateCert", "deleteCertificate", - "revokeCertificate" + "revokeCertificate", + "manageRenewal", + "renewCertificate" ] as const); const onRemoveCertificateSubmit = async (serialNumber: string) => { @@ -98,6 +102,8 @@ export const CertificatesSection = () => { )} + + { + const expiryDate = new Date(notAfter); + const now = new Date(); + const oneDayFromNow = new Date(now.getTime() + 24 * 60 * 60 * 1000); + return expiryDate <= oneDayFromNow; +}; + +const getAutoRenewalInfo = (certificate: TCertificate) => { + if (certificate.renewedByCertificateId) { + return { text: "Renewed", variant: "instance" as const }; + } + + const isRevoked = certificate.status === CertStatus.REVOKED; + const isExpired = new Date(certificate.notAfter) < new Date(); + const hasNoProfile = !certificate.profileId; + const isExpiringWithinDay = isExpiringWithinOneDay(certificate.notAfter); + + if (isRevoked) { + return { + text: "Not Available", + variant: "instance" as const, + tooltip: "Renewal is not available for revoked certificates" + }; + } + + if (isExpired) { + return { + text: "Not Available", + variant: "instance" as const, + tooltip: "Renewal is not available for expired certificates" + }; + } + + if (hasNoProfile) { + return { + text: "Not Available", + variant: "instance" as const, + tooltip: "Renewal requires a certificate profile" + }; + } + + if (certificate.hasPrivateKey === false) { + return { + text: "Not Available", + variant: "instance" as const, + tooltip: "Renewal is not available for certificates with externally generated private keys" + }; + } + + if (isExpiringWithinDay) { + return { + text: "Not Available", + variant: "instance" as const, + tooltip: "Auto-renewal is not available for certificates expiring within 24 hours" + }; + } + + if (certificate.renewalError) { + return { + text: "Failed", + variant: "danger" as const, + tooltip: certificate.renewalError + }; + } + + if (!certificate.renewBeforeDays) { + return { text: "Auto-Renewal Disabled", variant: "primary" as const }; + } + + const notAfterDate = new Date(certificate.notAfter); + const renewalDate = new Date( + notAfterDate.getTime() - certificate.renewBeforeDays * 24 * 60 * 60 * 1000 + ); + const now = new Date(); + + if (renewalDate <= now) { + return { text: "Due Now", variant: "danger" as const }; + } + + const daysUntilRenewal = Math.floor( + (renewalDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000) + ); + + if (daysUntilRenewal === 0) { + return { text: "Renews today", variant: "primary" as const }; + } + + if (daysUntilRenewal <= 7) { + return { text: `Renews in ${daysUntilRenewal}d`, variant: "primary" as const }; + } + + return { text: `Renews in ${daysUntilRenewal}d`, variant: "success" as const }; +}; + type Props = { handlePopUpOpen: ( popUpName: keyof UsePopUpState< - ["certificate", "deleteCertificate", "revokeCertificate", "certificateCert"] + [ + "certificate", + "deleteCertificate", + "revokeCertificate", + "certificateCert", + "manageRenewal", + "renewCertificate" + ] >, data?: { serialNumber?: string; commonName?: string; + certificateId?: string; + profileId?: string; + renewBeforeDays?: number; + ttlDays?: number; + notAfter?: string; + renewalError?: string; + renewedFromCertificateId?: string; + renewedByCertificateId?: string; } ) => void; }; @@ -61,6 +175,7 @@ const PER_PAGE_INIT = 25; export const CertificatesTable = ({ handlePopUpOpen }: Props) => { const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(PER_PAGE_INIT); + const { subscription } = useSubscription(); const { currentProject } = useProject(); const { data, isPending } = useListWorkspaceCertificates({ @@ -69,10 +184,11 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { limit: perPage }); - // Fetch CA data to determine capabilities + const { mutateAsync: updateRenewalConfig } = useUpdateRenewalConfig(); + const isLegacyTemplatesEnabled = subscription.pkiLegacyTemplates; + const { data: caData } = useListCasByProjectId(currentProject?.id ?? ""); - // Create mapping from caId to CA type for capability checking const caCapabilityMap = useMemo(() => { if (!caData) return {}; @@ -83,6 +199,35 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { return map; }, [caData]); + const handleDisableAutoRenewal = async (certificateId: string, commonName: string) => { + try { + if (!currentProject?.slug) { + createNotification({ + text: "Unable to disable auto-renewal: Project not found. Please refresh the page and try again.", + type: "error" + }); + return; + } + + await updateRenewalConfig({ + certificateId, + projectSlug: currentProject.slug, + enableAutoRenewal: false + }); + + createNotification({ + text: `Auto-renewal disabled for ${commonName}`, + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to disable auto-renewal. Please try again or contact support if the issue persists.", + type: "error" + }); + } + }; + return ( @@ -92,14 +237,24 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { + - {isPending && } + {isPending && } {!isPending && data?.certificates.map((certificate) => { const { variant, label } = getCertValidUntilBadgeDetails(certificate.notAfter); + const autoRenewalInfo = getAutoRenewalInfo(certificate); + + const isRevoked = certificate.status === CertStatus.REVOKED; + const isExpired = new Date(certificate.notAfter) < new Date(); + const isExpiringWithinDay = isExpiringWithinOneDay(certificate.notAfter); + const hasFailed = Boolean(certificate.renewalError); + const isAutoRenewalEnabled = Boolean( + certificate.renewBeforeDays && certificate.renewBeforeDays > 0 + ); return ( @@ -120,6 +275,25 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { ? format(new Date(certificate.notAfter), "yyyy-MM-dd") : "-"} +
Status Not Before Not AfterRenewal Status
{certificate.commonName} + {autoRenewalInfo && + (autoRenewalInfo.tooltip ? ( +
+ + {autoRenewalInfo.text} + + + + +
+ ) : ( + {autoRenewalInfo.text} + ))} +
@@ -151,31 +325,172 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { )} - - {(isAllowed) => ( - - handlePopUpOpen("certificate", { - serialNumber: certificate.serialNumber - }) - } - disabled={!isAllowed} - icon={} + {isLegacyTemplatesEnabled && ( + + {(isAllowed) => ( + + handlePopUpOpen("certificate", { + serialNumber: certificate.serialNumber + }) + } + disabled={!isAllowed} + icon={} + > + View Details + + )} + + )} + {/* Manage auto renewal option - not shown for failed renewals */} + {(() => { + const canManageRenewal = + certificate.profileId && + certificate.hasPrivateKey !== false && + !certificate.renewedByCertificateId && + !isRevoked && + !isExpired && + !hasFailed && + !isExpiringWithinDay; + + if (!canManageRenewal) return null; + + return ( + - View Details - - )} - + {(isAllowed) => { + return ( + { + const notAfterDate = new Date(certificate.notAfter); + const notBeforeDate = certificate.notBefore + ? new Date(certificate.notBefore) + : new Date( + notAfterDate.getTime() - 365 * 24 * 60 * 60 * 1000 + ); + const ttlDays = Math.max( + 1, + Math.ceil( + (notAfterDate.getTime() - notBeforeDate.getTime()) / + (24 * 60 * 60 * 1000) + ) + ); + handlePopUpOpen("manageRenewal", { + certificateId: certificate.id, + commonName: certificate.commonName, + profileId: certificate.profileId, + renewBeforeDays: certificate.renewBeforeDays, + ttlDays, + notAfter: certificate.notAfter, + renewalError: certificate.renewalError, + renewedFromCertificateId: + certificate.renewedFromCertificateId, + renewedByCertificateId: certificate.renewedByCertificateId + }); + }} + disabled={!isAllowed} + icon={} + > + {isAutoRenewalEnabled + ? "Manage auto renewal" + : "Enable auto renewal"} + + ); + }} + + ); + })()} + {/* Disable auto renewal option - only shown when auto renewal is active */} + {(() => { + const canDisableRenewal = + certificate.profileId && + certificate.hasPrivateKey !== false && + !certificate.renewedByCertificateId && + !isRevoked && + !isExpired && + !isExpiringWithinDay && + isAutoRenewalEnabled; + + if (!canDisableRenewal) return null; + + return ( + + {(isAllowed) => ( + { + await handleDisableAutoRenewal( + certificate.id, + certificate.commonName + ); + }} + disabled={!isAllowed} + icon={} + > + Disable auto renewal + + )} + + ); + })()} + {/* Manual renewal action for profile-issued certificates that are not revoked/expired (including failed ones) */} + {(() => { + const canRenew = + certificate.profileId && + certificate.hasPrivateKey !== false && + !certificate.renewedByCertificateId && + !isRevoked && + !isExpired; + + if (!canRenew) return null; + + return ( + + {(isAllowed) => ( + { + handlePopUpOpen("renewCertificate", { + certificateId: certificate.id, + commonName: certificate.commonName + }); + }} + disabled={!isAllowed} + icon={} + > + Renew Now + + )} + + ); + })()} {/* Only show revoke button if CA supports revocation */} {(() => { const caType = caCapabilityMap[certificate.caId]; - // If caId not found in map, assume CA supports revocation to avoid hiding revoke option const supportsRevocation = !caType || caSupportsCapability(caType, CaCapability.REVOKE_CERTIFICATES); diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/useCertificateTemplate.ts b/frontend/src/pages/cert-manager/CertificatesPage/components/useCertificateTemplate.ts index 871ad00a4..5499a2762 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/useCertificateTemplate.ts +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/useCertificateTemplate.ts @@ -11,6 +11,26 @@ import { mapTemplateSignatureAlgorithmToApi } from "@app/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/certificate-constants"; +const convertTemplateTtlToCertificateTtl = (templateTtl: string): string => { + const match = templateTtl.match(/^(\d+)([dmyh])$/); + if (!match) return templateTtl; + + const [, value, unit] = match; + const numValue = parseInt(value, 10); + + switch (unit) { + case "m": + return `${numValue * 30}d`; + case "y": + return `${numValue * 365}d`; + case "d": + case "h": + return templateTtl; + default: + return templateTtl; + } +}; + export type TemplateConstraints = { allowedKeyUsages: string[]; allowedExtendedKeyUsages: string[]; @@ -118,7 +138,7 @@ export const useCertificateTemplate = ( // Set TTL if available if (templateData.validity?.max) { - setValue("ttl", templateData.validity.max); + setValue("ttl", convertTemplateTtlToCertificateTtl(templateData.validity.max)); } // Handle SAN types diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx index 3f7553f83..ea82fd843 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx @@ -1,5 +1,7 @@ import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; +import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; @@ -13,7 +15,8 @@ import { ModalContent, Select, SelectItem, - TextArea + TextArea, + Tooltip } from "@app/components/v2"; import { useProject } from "@app/context"; import { useListCasByProjectId } from "@app/hooks/api/ca/queries"; @@ -67,7 +70,7 @@ const createSchema = z apiConfig: z .object({ autoRenew: z.boolean().optional(), - autoRenewDays: z.number().min(1).max(365).optional() + renewBeforeDays: z.number().min(1).max(365).optional() }) .optional() }) @@ -115,7 +118,7 @@ const editSchema = z apiConfig: z .object({ autoRenew: z.boolean().optional(), - autoRenewDays: z.number().min(1).max(365).optional() + renewBeforeDays: z.number().min(1).max(365).optional() }) .optional() }) @@ -183,7 +186,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" } profile.enrollmentType === "api" ? { autoRenew: profile.apiConfig?.autoRenew || false, - autoRenewDays: profile.apiConfig?.autoRenewDays || 30 + renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30 } : undefined } @@ -195,7 +198,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" } certificateTemplateId: "", apiConfig: { autoRenew: false, - autoRenewDays: 30 + renewBeforeDays: 30 } } }); @@ -225,7 +228,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" } profile.enrollmentType === "api" ? { autoRenew: profile.apiConfig?.autoRenew || false, - autoRenewDays: profile.apiConfig?.autoRenewDays || 30 + renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30 } : undefined }); @@ -389,7 +392,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" } } else { setValue("apiConfig", { autoRenew: false, - autoRenewDays: 30 + renewBeforeDays: 30 }); setValue("estConfig", undefined); } @@ -433,7 +436,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" } setValue("estConfig", undefined); setValue("apiConfig", { autoRenew: false, - autoRenewDays: 30 + renewBeforeDays: 30 }); } onChange(value); @@ -535,9 +538,18 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" } name="apiConfig.autoRenew" render={({ field: { value, onChange }, fieldState: { error } }) => ( - - Enable Auto-Renewal - +
+ + Enable Auto-Renewal By Default + + + + +
)} /> @@ -548,10 +560,10 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
( diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx index a44c4746b..c4115a4b7 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -96,7 +96,7 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => { case AppConnection.AWS: return ; case AppConnection.GitHub: - return ; + return ; case AppConnection.GitHubRadar: return ; case AppConnection.GCP: @@ -213,7 +213,11 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { return ; case AppConnection.GitHub: return ( - + ); case AppConnection.GitHubRadar: return ( diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubConnectionForm.tsx index 5faa7d5b0..5d7092c03 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubConnectionForm.tsx @@ -16,6 +16,7 @@ import { FormControl, Input, ModalClose, + SecretInput, Select, SelectItem, Tooltip @@ -48,28 +49,58 @@ import { type Props = { appConnection?: TGitHubConnection; projectId: string | undefined | null; + onSubmit: (formData: PatSchemaForm) => Promise; }; -const formSchema = genericAppConnectionFieldsSchema.extend({ +const rootSchema = genericAppConnectionFieldsSchema.extend({ app: z.literal(AppConnection.GitHub), - method: z.nativeEnum(GitHubConnectionMethod), - credentials: z - .union([ - z.object({ - instanceType: z.literal("cloud").optional(), - host: z.string().optional() - }), - z.object({ - instanceType: z.literal("server"), - host: z.string().min(1, "Required") - }) - ]) - .optional() + method: z.nativeEnum(GitHubConnectionMethod) }); +const baseCredentialsSchema = z.union([ + z.object({ + instanceType: z.literal("server"), + host: z.string().min(1, "Host is required for server instance type") + }), + z.object({ + instanceType: z.literal("cloud").optional(), + host: z.string().optional() + }) +]); + +const appSchema = rootSchema.extend({ + method: z.literal(GitHubConnectionMethod.App), + credentials: baseCredentialsSchema +}); + +const oauthSchema = rootSchema.extend({ + method: z.literal(GitHubConnectionMethod.OAuth), + credentials: baseCredentialsSchema +}); + +const patSchema = rootSchema.extend({ + method: z.literal(GitHubConnectionMethod.Pat), + credentials: z.union([ + z.object({ + instanceType: z.literal("server"), + host: z.string().min(1, "Host is required for server instance type"), + personalAccessToken: z.string().min(1, "Personal Access Token is required") + }), + z.object({ + instanceType: z.literal("cloud").optional(), + host: z.string().optional(), + personalAccessToken: z.string().min(1, "Personal Access Token is required") + }) + ]) +}); + +type PatSchemaForm = z.infer; + +const formSchema = z.discriminatedUnion("method", [appSchema, oauthSchema, patSchema]); + type FormData = z.infer; -export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => { +export const GitHubConnectionForm = ({ appConnection, projectId, onSubmit }: Props) => { const isUpdate = Boolean(appConnection); const [isRedirecting, setIsRedirecting] = useState(false); @@ -106,7 +137,12 @@ export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => { const returnUrl = useGetAppConnectionOauthReturnUrl(); - const onSubmit = (formData: FormData) => { + const submitHandler = async (formData: FormData) => { + if (formData.method === GitHubConnectionMethod.Pat) { + await onSubmit(formData); + return; + } + setIsRedirecting(true); const state = crypto.randomBytes(16).toString("hex"); localStorage.setItem("latestCSRFToken", state); @@ -151,15 +187,26 @@ export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => { case GitHubConnectionMethod.App: isMissingConfig = !appClientSlug; break; + case GitHubConnectionMethod.Pat: + isMissingConfig = false; + break; default: throw new Error(`Unhandled GitHub Connection method: ${selectedMethod}`); } const methodDetails = getAppConnectionMethodDetails(selectedMethod); + const getButtonText = () => { + if (selectedMethod === GitHubConnectionMethod.Pat) { + return isUpdate ? "Update Connection" : "Create Connection"; + } + + return isUpdate ? "Reconnect to GitHub" : "Connect to GitHub"; + }; + return ( -
+ {!isUpdate && } { )} /> + {selectedMethod === GitHubConnectionMethod.Pat && ( + ( + + onChange(e.target.value)} + /> + + )} + /> + )} @@ -310,7 +377,7 @@ export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => { isLoading={isSubmitting || isRedirecting} isDisabled={isSubmitting || (!isUpdate && !isDirty) || isMissingConfig || isRedirecting} > - {isUpdate ? "Reconnect to GitHub" : "Connect to GitHub"} + {getButtonText()} + + + +
+ + + ); +}; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx index 8b553e656..b9599be2d 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx @@ -8,6 +8,7 @@ import { import { DiscriminativePick } from "@app/types"; import { PamAccountHeader } from "../PamAccountHeader"; +import { MySQLAccountForm } from "./MySQLAccountForm"; import { PostgresAccountForm } from "./PostgresAccountForm"; type FormProps = { @@ -35,10 +36,7 @@ const CreateForm = ({ const createPamAccount = useCreatePamAccount(); const onSubmit = async ( - formData: DiscriminativePick< - TPamAccount, - "name" | "description" | "credentials" | "rotationEnabled" | "rotationIntervalSeconds" - > + formData: DiscriminativePick ) => { try { const account = await createPamAccount.mutateAsync({ @@ -72,6 +70,10 @@ const CreateForm = ({ resourceType={resourceType} /> ); + case PamResourceType.MySQL: + return ( + + ); default: throw new Error(`Unhandled resource: ${resourceType}`); } @@ -81,10 +83,7 @@ const UpdateForm = ({ account, onComplete }: UpdateFormProps) => { const updatePamAccount = useUpdatePamAccount(); const onSubmit = async ( - formData: DiscriminativePick< - TPamAccount, - "name" | "description" | "credentials" | "rotationEnabled" | "rotationIntervalSeconds" - > + formData: DiscriminativePick ) => { try { const updatedAccount = await updatePamAccount.mutateAsync({ @@ -110,6 +109,8 @@ const UpdateForm = ({ account, onComplete }: UpdateFormProps) => { switch (account.resource.resourceType) { case PamResourceType.Postgres: return ; + case PamResourceType.MySQL: + return ; default: throw new Error(`Unhandled resource: ${account.resource.resourceType}`); } diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx index 3e5994344..6d361877e 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx @@ -1,16 +1,21 @@ +import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, ModalClose } from "@app/components/v2"; -import { PamResourceType, TPostgresAccount, useGetPamResourceById } from "@app/hooks/api/pam"; +import { + PamResourceType, + TPostgresAccount, + TPostgresResource, + useGetPamResourceById +} from "@app/hooks/api/pam"; import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants"; -import { BaseSqlAccountSchema } from "./shared/sql-account-schemas"; -import { SqlAccountFields } from "./shared/SqlAccountFields"; import { GenericAccountFields, genericAccountFieldsSchema } from "./GenericAccountFields"; import { RotateAccountFields, rotateAccountFieldsSchema } from "./RotateAccountFields"; +import { BaseSqlAccountSchema } from "./shared/sql-account-schemas"; +import { SqlAccountFields } from "./shared/SqlAccountFields"; type Props = { account?: TPostgresAccount; @@ -56,7 +61,9 @@ export const PostgresAccountForm = ({ account, resourceId, resourceType, onSubmi if (account) { setRotationCredentialsConfigured(account.resource.rotationCredentialsConfigured); } else { - setRotationCredentialsConfigured(!!resource?.rotationAccountCredentials); + setRotationCredentialsConfigured( + !!(resource as TPostgresResource)?.rotationAccountCredentials + ); } }, [account, resource]); diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/MySQLResourceForm.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/MySQLResourceForm.tsx new file mode 100644 index 000000000..b7c2996d3 --- /dev/null +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/MySQLResourceForm.tsx @@ -0,0 +1,84 @@ +import { useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { Button, ModalClose } from "@app/components/v2"; +import { PamResourceType, TMySQLResource } from "@app/hooks/api/pam"; + +import { BaseSqlResourceSchema } from "./shared/sql-resource-schemas"; +import { SqlResourceFields } from "./shared/SqlResourceFields"; +import { GenericResourceFields, genericResourceFieldsSchema } from "./GenericResourceFields"; + +type Props = { + resource?: TMySQLResource; + onSubmit: (formData: FormData) => Promise; +}; + +const formSchema = genericResourceFieldsSchema.extend({ + resourceType: z.literal(PamResourceType.MySQL), + connectionDetails: BaseSqlResourceSchema.extend({ + database: z.string().trim().optional().default("") + }) +}); + +type FormData = z.infer; + +export const MySQLResourceForm = ({ resource, onSubmit }: Props) => { + const isUpdate = Boolean(resource); + const [selectedTabIndex, setSelectedTabIndex] = useState(0); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: resource ?? { + resourceType: PamResourceType.MySQL, + connectionDetails: { + host: "", + port: 3306, + database: "", + sslEnabled: true, + sslRejectUnauthorized: true, + sslCertificate: undefined + } + } + }); + + const { + handleSubmit, + formState: { isSubmitting, isDirty } + } = form; + + return ( + +
{ + setSelectedTabIndex(0); + handleSubmit(onSubmit)(e); + }} + > + + +
+ + + + +
+ +
+ ); +}; diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx index 8cfdc8582..2bc54e7cd 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx @@ -9,6 +9,7 @@ import { import { DiscriminativePick } from "@app/types"; import { PamResourceHeader } from "../PamResourceHeader"; +import { MySQLResourceForm } from "./MySQLResourceForm"; import { PostgresResourceForm } from "./PostgresResourceForm"; type FormProps = { @@ -57,6 +58,8 @@ const CreateForm = ({ resourceType, onComplete, projectId }: CreateFormProps) => switch (resourceType) { case PamResourceType.Postgres: return ; + case PamResourceType.MySQL: + return ; default: throw new Error(`Unhandled resource: ${resourceType}`); } @@ -92,8 +95,10 @@ const UpdateForm = ({ resource, onComplete }: UpdateFormProps) => { switch (resource.resourceType) { case PamResourceType.Postgres: return ; + case PamResourceType.MySQL: + return ; default: - throw new Error(`Unhandled resource: ${resource.resourceType}`); + throw new Error(`Unhandled resource: ${(resource as any).resourceType}`); } }; diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlResourceFields.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlResourceFields.tsx index 888b62984..c3f3982ec 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlResourceFields.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlResourceFields.tsx @@ -110,7 +110,7 @@ export const SqlResourceFields = ({ setSelectedTabIndex, selectedTabIndex }: Pro errorText={error?.message} isError={Boolean(error?.message)} className={sslEnabled ? "" : "opacity-50"} - label="SSL Certificate" + label="Trusted CA SSL Certificate" isOptional >