diff --git a/.dockerignore b/.dockerignore index 74bedb17..042dcf2f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -27,3 +27,4 @@ bruno/ LICENSE CONTRIBUTING.md dist +.git diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 00000000..8ca8fac0 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,19 @@ +services: + # PostgreSQL Service + db: + image: postgres:17 # Use the PostgreSQL 17 image + container_name: dev_postgres # Name your PostgreSQL container + environment: + POSTGRES_DB: postgres # Default database name + POSTGRES_USER: postgres # Default user + POSTGRES_PASSWORD: password # Default password (change for production!) + ports: + - "5432:5432" # Map host port 5432 to container port 5432 + restart: no + + redis: + image: redis:latest # Use the latest Redis image + container_name: dev_redis # Name your Redis container + ports: + - "6379:6379" # Map host port 6379 to container port 6379 + restart: no diff --git a/drizzle.pg.config.ts b/drizzle.pg.config.ts index 14aeba5b..4d1f1e43 100644 --- a/drizzle.pg.config.ts +++ b/drizzle.pg.config.ts @@ -3,7 +3,7 @@ import path from "path"; export default defineConfig({ dialect: "postgresql", - schema: path.join("server", "db", "pg", "schema.ts"), + schema: [path.join("server", "db", "pg", "schema.ts")], out: path.join("server", "migrations"), verbose: true, dbCredentials: { diff --git a/install/config/crowdsec/profiles.yaml b/install/config/crowdsec/profiles.yaml index 3796b47f..0632f51d 100644 --- a/install/config/crowdsec/profiles.yaml +++ b/install/config/crowdsec/profiles.yaml @@ -1,4 +1,4 @@ -name: captcha_remediation +iame: captcha_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() contains "http" decisions: diff --git a/messages/en-US.json b/messages/en-US.json index 76610e35..acbcdbe0 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -10,7 +10,8 @@ "setupErrorIdentifier": "Organization ID is already taken. Please choose a different one.", "componentsErrorNoMemberCreate": "You are not currently a member of any organizations. Create an organization to get started.", "componentsErrorNoMember": "You are not currently a member of any organizations.", - "welcome": "Welcome to Pangolin", + "welcome": "Welcome!", + "welcomeTo": "Welcome to", "componentsCreateOrg": "Create an Organization", "componentsMember": "You're a member of {count, plural, =0 {no organization} =1 {one organization} other {# organizations}}.", "componentsInvalidKey": "Invalid or expired license keys detected. Follow license terms to continue using all features.", @@ -777,8 +778,6 @@ "orgPoliciesAdd": "Add Organization Policy", "orgRequired": "Organization is required", "error": "Error", - "refreshError": "Failed to refresh data", - "refresh": "Refresh", "success": "Success", "orgPolicyAddedDescription": "Policy added successfully", "orgPolicyUpdatedDescription": "Policy updated successfully", @@ -1094,6 +1093,7 @@ "sidebarIdentityProviders": "Identity Providers", "sidebarLicense": "License", "sidebarClients": "Clients", + "sidebarDomains": "Domains", "enableDockerSocket": "Enable Docker Socket", "enableDockerSocketDescription": "Enable Docker Socket discovery for populating container information. Socket path must be provided to Newt.", "enableDockerSocketLink": "Learn More", @@ -1138,6 +1138,50 @@ "initialSetupDescription": "Create the intial server admin account. Only one server admin can exist. You can always change these credentials later.", "createAdminAccount": "Create Admin Account", "setupErrorCreateAdmin": "An error occurred while creating the server admin account.", + "certificateStatus": "Certificate Status", + "loading": "Loading", + "restart": "Restart", + "domains": "Domains", + "domainsDescription": "Manage domains for your organization", + "domainsSearch": "Search domains...", + "domainAdd": "Add Domain", + "domainAddDescription": "Register a new domain with your organization", + "domainCreate": "Create Domain", + "domainCreatedDescription": "Domain created successfully", + "domainDeletedDescription": "Domain deleted successfully", + "domainQuestionRemove": "Are you sure you want to remove the domain {domain} from your account?", + "domainMessageRemove": "Once removed, the domain will no longer be associated with your account.", + "domainMessageConfirm": "To confirm, please type the domain name below.", + "domainConfirmDelete": "Confirm Delete Domain", + "domainDelete": "Delete Domain", + "domain": "Domain", + "selectDomainTypeNsName": "Domain Delegation (NS)", + "selectDomainTypeNsDescription": "This domain and all its subdomains. Use this when you want to control an entire domain zone.", + "selectDomainTypeCnameName": "Single Domain (CNAME)", + "selectDomainTypeCnameDescription": "Just this specific domain. Use this for individual subdomains or specific domain entries.", + "domainDelegation": "Single Domain", + "selectType": "Select a type", + "actions": "Actions", + "refresh": "Refresh", + "refreshError": "Failed to refresh data", + "verified": "Verified", + "pending": "Pending", + "sidebarBilling": "Billing", + "billing": "Billing", + "orgBillingDescription": "Manage your billing information and subscriptions", + "github": "GitHub", + "pangolinHosted": "Pangolin Hosted", + "fossorial": "Fossorial", + "completeAccountSetup": "Complete Account Setup", + "completeAccountSetupDescription": "Set your password to get started", + "accountSetupSent": "We'll send an account setup code to this email address.", + "accountSetupCode": "Setup Code", + "accountSetupCodeDescription": "Check your email for the setup code.", + "passwordCreate": "Create Password", + "passwordCreateConfirm": "Confirm Password", + "accountSetupSubmit": "Send Setup Code", + "completeSetup": "Complete Setup", + "accountSetupSuccess": "Account setup completed! Welcome to Pangolin!", "documentation": "Documentation", "saveAllSettings": "Save All Settings", "settingsUpdated": "Settings updated", @@ -1147,5 +1191,23 @@ "sidebarCollapse": "Collapse", "sidebarExpand": "Expand", "newtUpdateAvailable": "Update Available", - "newtUpdateAvailableInfo": "A new version of Newt is available. Please update to the latest version for the best experience." + "newtUpdateAvailableInfo": "A new version of Newt is available. Please update to the latest version for the best experience.", + "domainPickerEnterDomain": "Enter your domain", + "domainPickerPlaceholder": "myapp.example.com, api.v1.mydomain.com, or just myapp", + "domainPickerDescription": "Enter a full domain, subdomain, or just a name to see available options", + "domainPickerTabAll": "All", + "domainPickerTabOrganization": "Organization", + "domainPickerTabProvided": "Provided", + "domainPickerSortAsc": "A-Z", + "domainPickerSortDesc": "Z-A", + "domainPickerCheckingAvailability": "Checking availability...", + "domainPickerNoMatchingDomains": "No matching domains found for \"{userInput}\". Try a different domain or check your organization's domain settings.", + "domainPickerOrganizationDomains": "Organization Domains", + "domainPickerProvidedDomains": "Provided Domains", + "domainPickerSubdomain": "Subdomain: {subdomain}", + "domainPickerNamespace": "Namespace: {namespace}", + "domainPickerShowMore": "Show More", + "domainNotFound": "Domain Not Found", + "domainNotFoundDescription": "This resource is disabled because the domain no longer exists our system. Please set a new domain for this resource.", + "failed": "Failed" } diff --git a/package-lock.json b/package-lock.json index c112465f..4fd8e822 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "SEE LICENSE IN LICENSE AND README.md", "dependencies": { "@asteasolutions/zod-to-openapi": "^7.3.2", + "@aws-sdk/client-s3": "3.837.0", "@hookform/resolvers": "3.9.1", "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "1.0.1", @@ -83,6 +84,7 @@ "react-icons": "^5.5.0", "rebuild": "0.1.2", "semver": "7.7.2", + "stripe": "18.2.1", "swagger-ui-express": "^5.0.1", "tailwind-merge": "2.6.0", "tw-animate-css": "^1.3.3", @@ -109,6 +111,7 @@ "@types/jsonwebtoken": "^9.0.9", "@types/node": "^22", "@types/nodemailer": "6.4.17", + "@types/pg": "8.15.4", "@types/react": "19.1.7", "@types/react-dom": "19.1.6", "@types/semver": "7.7.0", @@ -155,9 +158,9 @@ } }, "node_modules/@asteasolutions/zod-to-openapi": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@asteasolutions/zod-to-openapi/-/zod-to-openapi-7.3.3.tgz", - "integrity": "sha512-ioiw+R+gBGAUwmDp+/gJA16tedBivzDaji5wOvWej0ZYDE0CXTSSfJfXbrBIuWKh6JQhuXgNDniJdeDueKUZTA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@asteasolutions/zod-to-openapi/-/zod-to-openapi-7.3.4.tgz", + "integrity": "sha512-/2rThQ5zPi9OzVwes6U7lK1+Yvug0iXu25olp7S0XsYmOqnyMfxH7gdSQjn/+DSOHRg7wnotwGJSyL+fBKdnEA==", "license": "MIT", "dependencies": { "openapi3-ts": "^4.1.2" @@ -166,6 +169,876 @@ "zod": "^3.20.2" } }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.837.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.837.0.tgz", + "integrity": "sha512-sBjPPG30HIfNwpzWuajCDf7agb4YAxPFFpsp3kwgptJF8PEi0HzQg64bskquMzjqLC2tXsn5rKtDVpQOvs29MQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/credential-provider-node": "3.835.0", + "@aws-sdk/middleware-bucket-endpoint": "3.830.0", + "@aws-sdk/middleware-expect-continue": "3.821.0", + "@aws-sdk/middleware-flexible-checksums": "3.835.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-location-constraint": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-sdk-s3": "3.835.0", + "@aws-sdk/middleware-ssec": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.835.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/signature-v4-multi-region": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.835.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/eventstream-serde-browser": "^4.0.4", + "@smithy/eventstream-serde-config-resolver": "^4.1.2", + "@smithy/eventstream-serde-node": "^4.0.4", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-blob-browser": "^4.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.5", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.835.0.tgz", + "integrity": "sha512-4J19IcBKU5vL8yw/YWEvbwEGcmCli0rpRyxG53v0K5/3weVPxVBbKfkWcjWVQ4qdxNz2uInfbTde4BRBFxWllQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.835.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.835.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.835.0.tgz", + "integrity": "sha512-7mnf4xbaLI8rkDa+w6fUU48dG6yDuOgLXEPe4Ut3SbMp1ceJBPMozNHbCwkiyHk3HpxZYf8eVy0wXhJMrxZq5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.5.3", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.835.0.tgz", + "integrity": "sha512-U9LFWe7+ephNyekpUbzT7o6SmJTmn6xkrPkE0D7pbLojnPVi/8SZKyjtgQGIsAv+2kFkOCqMOIYUKd/0pE7uew==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.835.0.tgz", + "integrity": "sha512-jCdNEsQklil7frDm/BuVKl4ubVoQHRbV6fnkOjmxAJz0/v7cR8JP0jBGlqKKzh3ROh5/vo1/5VUZbCTLpc9dSg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.835.0.tgz", + "integrity": "sha512-nqF6rYRAnJedmvDfrfKygzyeADcduDvtvn7GlbQQbXKeR2l7KnCdhuxHa0FALLvspkHiBx7NtInmvnd5IMuWsw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/credential-provider-env": "3.835.0", + "@aws-sdk/credential-provider-http": "3.835.0", + "@aws-sdk/credential-provider-process": "3.835.0", + "@aws-sdk/credential-provider-sso": "3.835.0", + "@aws-sdk/credential-provider-web-identity": "3.835.0", + "@aws-sdk/nested-clients": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.835.0.tgz", + "integrity": "sha512-77B8elyZlaEd7vDYyCnYtVLuagIBwuJ0AQ98/36JMGrYX7TT8UVAhiDAfVe0NdUOMORvDNFfzL06VBm7wittYw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.835.0", + "@aws-sdk/credential-provider-http": "3.835.0", + "@aws-sdk/credential-provider-ini": "3.835.0", + "@aws-sdk/credential-provider-process": "3.835.0", + "@aws-sdk/credential-provider-sso": "3.835.0", + "@aws-sdk/credential-provider-web-identity": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.835.0.tgz", + "integrity": "sha512-qXkTt5pAhSi2Mp9GdgceZZFo/cFYrA735efqi/Re/nf0lpqBp8mRM8xv+iAaPHV4Q10q0DlkbEidT1DhxdT/+w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.835.0.tgz", + "integrity": "sha512-jAiEMryaPFXayYGszrc7NcgZA/zrrE3QvvvUBh/Udasg+9Qp5ZELdJCm/p98twNyY9n5i6Ex6VgvdxZ7+iEheQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.835.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/token-providers": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.835.0.tgz", + "integrity": "sha512-zfleEFXDLlcJ7cyfS4xSyCRpd8SVlYZfH3rp0pg2vPYKbnmXVE0r+gPIYXl4L+Yz4A2tizYl63nKCNdtbxadog==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/nested-clients": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.830.0.tgz", + "integrity": "sha512-ElVeCReZSH5Ds+/pkL5ebneJjuo8f49e9JXV1cYizuH0OAOQfYaBU9+M+7+rn61pTttOFE8W//qKzrXBBJhfMg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.821.0.tgz", + "integrity": "sha512-zAOoSZKe1njOrtynvK6ZORU57YGv5I7KP4+rwOvUN3ZhJbQ7QPf8gKtFUCYAPRMegaXCKF/ADPtDZBAmM+zZ9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.835.0.tgz", + "integrity": "sha512-9ezorQYlr5cQY28zWAReFhNKUTaXsi3TMvXIagMRrSeWtQ7R6TCYnt91xzHRCmFR2kp3zLI+dfoeH+wF3iCKUw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.821.0.tgz", + "integrity": "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.821.0.tgz", + "integrity": "sha512-sKrm80k0t3R0on8aA/WhWFoMaAl4yvdk+riotmMElLUpcMcRXAd1+600uFVrxJqZdbrKQ0mjX0PjT68DlkYXLg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.821.0.tgz", + "integrity": "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.821.0.tgz", + "integrity": "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.835.0.tgz", + "integrity": "sha512-oPebxpVf9smInHhevHh3APFZagGU+4RPwXEWv9YtYapFvsMq+8QXFvOfxfVZ/mwpe0JVG7EiJzL9/9Kobmts8Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/core": "^3.5.3", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.821.0.tgz", + "integrity": "sha512-YYi1Hhr2AYiU/24cQc8HIB+SWbQo6FBkMYojVuz/zgrtkFmALxENGF/21OPg7f/QWd+eadZJRxCjmRwh5F2Cxg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.835.0.tgz", + "integrity": "sha512-2gmAYygeE/gzhyF2XlkcbMLYFTbNfV61n+iCFa/ZofJHXYE+RxSyl5g4kujLEs7bVZHmjQZJXhprVSkGccq3/w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@smithy/core": "^3.5.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.835.0.tgz", + "integrity": "sha512-UtmOO0U5QkicjCEv+B32qqRAnS7o2ZkZhC+i3ccH1h3fsfaBshpuuNBwOYAzRCRBeKW5fw3ANFrV/+2FTp4jWg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.835.0", + "@aws-sdk/middleware-host-header": "3.821.0", + "@aws-sdk/middleware-logger": "3.821.0", + "@aws-sdk/middleware-recursion-detection": "3.821.0", + "@aws-sdk/middleware-user-agent": "3.835.0", + "@aws-sdk/region-config-resolver": "3.821.0", + "@aws-sdk/types": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", + "@aws-sdk/util-user-agent-browser": "3.821.0", + "@aws-sdk/util-user-agent-node": "3.835.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.5.3", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.12", + "@smithy/middleware-retry": "^4.1.13", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.20", + "@smithy/util-defaults-mode-node": "^4.0.20", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.821.0.tgz", + "integrity": "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.835.0.tgz", + "integrity": "sha512-rEtJH4dIwJYlXXe5rIH+uTCQmd2VIjuaoHlDY3Dr4nxF6po6U7vKsLfybIU2tgflGVqoqYQnXsfW/kj/Rh+/ow==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.835.0.tgz", + "integrity": "sha512-zN1P3BE+Rv7w7q/CDA8VCQox6SE9QTn0vDtQ47AHA3eXZQQgYzBqgoLgJxR9rKKBIRGZqInJa/VRskLL95VliQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.835.0", + "@aws-sdk/nested-clients": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.821.0.tgz", + "integrity": "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.828.0.tgz", + "integrity": "sha512-RvKch111SblqdkPzg3oCIdlGxlQs+k+P7Etory9FmxPHyPDvsP1j1c74PmgYqtzzMWmoXTjd+c9naUHh9xG8xg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.804.0.tgz", + "integrity": "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.821.0.tgz", + "integrity": "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.821.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.835.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.835.0.tgz", + "integrity": "sha512-gY63QZ4W5w9JYHYuqvUxiVGpn7IbCt1ODPQB0ZZwGGr3WRmK+yyZxCtFjbYhEQDQLgTWpf8YgVxgQLv2ps0PJg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.835.0", + "@aws-sdk/types": "3.821.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -359,6 +1232,37 @@ "@noble/ciphers": "^1.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", + "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", + "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild-kit/core-utils": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", @@ -371,6 +1275,57 @@ "source-map-support": "^0.5.21" } }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", @@ -388,6 +1343,312 @@ "node": ">=12" } }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/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==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", @@ -454,6 +1715,74 @@ "typescript": "*" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", @@ -471,6 +1800,346 @@ "node": ">=18" } }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -804,6 +2473,28 @@ "@img/sharp-libvips-darwin-arm64": "1.1.0" } }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.2.tgz", + "integrity": "sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.1.0" + } + }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz", @@ -820,6 +2511,342 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz", + "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz", + "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz", + "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz", + "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz", + "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz", + "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz", + "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz", + "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.2.tgz", + "integrity": "sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.2.tgz", + "integrity": "sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.2.tgz", + "integrity": "sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.2.tgz", + "integrity": "sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.2.tgz", + "integrity": "sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.2.tgz", + "integrity": "sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.2.tgz", + "integrity": "sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.2.tgz", + "integrity": "sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.2.tgz", + "integrity": "sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.2.tgz", + "integrity": "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@ioredis/commands": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", @@ -930,6 +2957,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", + "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.9.0" + } + }, "node_modules/@next/env": { "version": "15.3.3", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.3.tgz", @@ -968,6 +3007,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -983,6 +3023,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -998,6 +3039,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1013,6 +3055,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1028,6 +3071,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1043,6 +3087,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1058,6 +3103,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1133,6 +3179,38 @@ "@node-rs/argon2-win32-x64-msvc": "2.0.2" } }, + "node_modules/@node-rs/argon2-android-arm-eabi": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-2.0.2.tgz", + "integrity": "sha512-DV/H8p/jt40lrao5z5g6nM9dPNPGEHL+aK6Iy/og+dbL503Uj0AHLqj1Hk9aVUSCNnsDdUEKp4TVMi0YakDYKw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-android-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-2.0.2.tgz", + "integrity": "sha512-1LKwskau+8O1ktKx7TbK7jx1oMOMt4YEXZOdSNIar1TQKxm6isZ0cRXgHLibPHEcNHgYRsJWDE9zvDGBB17QDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@node-rs/argon2-darwin-arm64": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-2.0.2.tgz", @@ -1149,6 +3227,182 @@ "node": ">= 10" } }, + "node_modules/@node-rs/argon2-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-2.0.2.tgz", + "integrity": "sha512-vNPfkLj5Ij5111UTiYuwgxMqE7DRbOS2y58O2DIySzSHbcnu+nipmRKg+P0doRq6eKIJStyBK8dQi5Ic8pFyDw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-freebsd-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-2.0.2.tgz", + "integrity": "sha512-M8vQZk01qojQfCqQU0/O1j1a4zPPrz93zc9fSINY7Q/6RhQRBCYwDw7ltDCZXg5JRGlSaeS8cUXWyhPGar3cGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm-gnueabihf": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-2.0.2.tgz", + "integrity": "sha512-7EmmEPHLzcu0G2GDh30L6G48CH38roFC2dqlQJmtRCxs6no3tTE/pvgBGatTp/o2n2oyOJcfmgndVFcUpwMnww==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm64-gnu": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-2.0.2.tgz", + "integrity": "sha512-6lsYh3Ftbk+HAIZ7wNuRF4SZDtxtFTfK+HYFAQQyW7Ig3LHqasqwfUKRXVSV5tJ+xTnxjqgKzvZSUJCAyIfHew==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm64-musl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-2.0.2.tgz", + "integrity": "sha512-p3YqVMNT/4DNR67tIHTYGbedYmXxW9QlFmF39SkXyEbGQwpgSf6pH457/fyXBIYznTU/smnG9EH+C1uzT5j4hA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-x64-gnu": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-2.0.2.tgz", + "integrity": "sha512-ZM3jrHuJ0dKOhvA80gKJqBpBRmTJTFSo2+xVZR+phQcbAKRlDMSZMFDiKbSTnctkfwNFtjgDdh5g1vaEV04AvA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-x64-musl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-2.0.2.tgz", + "integrity": "sha512-of5uPqk7oCRF/44a89YlWTEfjsftPywyTULwuFDKyD8QtVZoonrJR6ZWvfFE/6jBT68S0okAkAzzMEdBVWdxWw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-wasm32-wasi": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-2.0.2.tgz", + "integrity": "sha512-U3PzLYKSQYzTERstgtHLd4ZTkOF9co57zTXT77r0cVUsleGZOrd6ut7rHzeWwoJSiHOVxxa0OhG1JVQeB7lLoQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/argon2-win32-arm64-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-2.0.2.tgz", + "integrity": "sha512-Eisd7/NM0m23ijrGr6xI2iMocdOuyl6gO27gfMfya4C5BODbUSP7ljKJ7LrA0teqZMdYHesRDzx36Js++/vhiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-win32-ia32-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-2.0.2.tgz", + "integrity": "sha512-GsE2ezwAYwh72f9gIjbGTZOf4HxEksb5M2eCaj+Y5rGYVwAdt7C12Q2e9H5LRYxWcFvLH4m4jiSZpQQ4upnPAQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-win32-x64-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-2.0.2.tgz", + "integrity": "sha512-cJxWXanH4Ew9CfuZ4IAEiafpOBCe97bzoKowHCGk5lG/7kR4WF/eknnBlHW9m8q7t10mKq75kruPLtbSDqgRTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@node-rs/bcrypt": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@node-rs/bcrypt/-/bcrypt-1.9.0.tgz", @@ -1178,6 +3432,38 @@ "@node-rs/bcrypt-win32-x64-msvc": "1.9.0" } }, + "node_modules/@node-rs/bcrypt-android-arm-eabi": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-android-arm-eabi/-/bcrypt-android-arm-eabi-1.9.0.tgz", + "integrity": "sha512-nOCFISGtnodGHNiLrG0WYLWr81qQzZKYfmwHc7muUeq+KY0sQXyHOwZk9OuNQAWv/lnntmtbwkwT0QNEmOyLvA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/bcrypt-android-arm64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-android-arm64/-/bcrypt-android-arm64-1.9.0.tgz", + "integrity": "sha512-+ZrIAtigVmjYkqZQTThHVlz0+TG6D+GDHWhVKvR2DifjtqJ0i+mb9gjo++hN+fWEQdWNGxKCiBBjwgT4EcXd6A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@node-rs/bcrypt-darwin-arm64": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-darwin-arm64/-/bcrypt-darwin-arm64-1.9.0.tgz", @@ -1194,6 +3480,215 @@ "node": ">= 10" } }, + "node_modules/@node-rs/bcrypt-darwin-x64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-darwin-x64/-/bcrypt-darwin-x64-1.9.0.tgz", + "integrity": "sha512-4pTKGawYd7sNEjdJ7R/R67uwQH1VvwPZ0SSUMmeNHbxD5QlwAPXdDH11q22uzVXsvNFZ6nGQBg8No5OUGpx6Ug==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/bcrypt-freebsd-x64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-freebsd-x64/-/bcrypt-freebsd-x64-1.9.0.tgz", + "integrity": "sha512-UmWzySX4BJhT/B8xmTru6iFif3h0Rpx3TqxRLCcbgmH43r7k5/9QuhpiyzpvKGpKHJCFNm4F3rC2wghvw5FCIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/bcrypt-linux-arm-gnueabihf": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm-gnueabihf/-/bcrypt-linux-arm-gnueabihf-1.9.0.tgz", + "integrity": "sha512-8qoX4PgBND2cVwsbajoAWo3NwdfJPEXgpCsZQZURz42oMjbGyhhSYbovBCskGU3EBLoC8RA2B1jFWooeYVn5BA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/bcrypt-linux-arm64-gnu": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-gnu/-/bcrypt-linux-arm64-gnu-1.9.0.tgz", + "integrity": "sha512-TuAC6kx0SbcIA4mSEWPi+OCcDjTQUMl213v5gMNlttF+D4ieIZx6pPDGTaMO6M2PDHTeCG0CBzZl0Lu+9b0c7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/bcrypt-linux-arm64-musl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-musl/-/bcrypt-linux-arm64-musl-1.9.0.tgz", + "integrity": "sha512-/sIvKDABOI8QOEnLD7hIj02BVaNOuCIWBKvxcJOt8+TuwJ6zmY1UI5kSv9d99WbiHjTp97wtAUbZQwauU4b9ew==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/bcrypt-linux-x64-gnu": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-x64-gnu/-/bcrypt-linux-x64-gnu-1.9.0.tgz", + "integrity": "sha512-DyyhDHDsLBsCKz1tZ1hLvUZSc1DK0FU0v52jK6IBQxrj24WscSU9zZe7ie/V9kdmA4Ep57BfpWX8Dsa2JxGdgQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/bcrypt-linux-x64-musl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-x64-musl/-/bcrypt-linux-x64-musl-1.9.0.tgz", + "integrity": "sha512-duIiuqQ+Lew8ASSAYm6ZRqcmfBGWwsi81XLUwz86a2HR7Qv6V4yc3ZAUQovAikhjCsIqe8C11JlAZSK6+PlXYg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/bcrypt-wasm32-wasi": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-wasm32-wasi/-/bcrypt-wasm32-wasi-1.9.0.tgz", + "integrity": "sha512-ylaGmn9Wjwv/D5lxtawttx3H6Uu2WTTR7lWlRHGT6Ga/MB1Vj4OjSGUW8G8zIVnKuXpGbZ92pgHlt4HUpSLctw==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^0.45.0", + "@emnapi/runtime": "^0.45.0", + "@tybys/wasm-util": "^0.8.1", + "memfs-browser": "^3.4.13000" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/bcrypt-wasm32-wasi/node_modules/@emnapi/core": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-0.45.0.tgz", + "integrity": "sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@node-rs/bcrypt-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", + "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@node-rs/bcrypt-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.8.3.tgz", + "integrity": "sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@node-rs/bcrypt-win32-arm64-msvc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-arm64-msvc/-/bcrypt-win32-arm64-msvc-1.9.0.tgz", + "integrity": "sha512-2h86gF7QFyEzODuDFml/Dp1MSJoZjxJ4yyT2Erf4NkwsiA5MqowUhUsorRwZhX6+2CtlGa7orbwi13AKMsYndw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/bcrypt-win32-ia32-msvc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-ia32-msvc/-/bcrypt-win32-ia32-msvc-1.9.0.tgz", + "integrity": "sha512-kqxalCvhs4FkN0+gWWfa4Bdy2NQAkfiqq/CEf6mNXC13RSV673Ev9V8sRlQyNpCHCNkeXfOT9pgoBdJmMs9muA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/bcrypt-win32-x64-msvc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-x64-msvc/-/bcrypt-win32-x64-msvc-1.9.0.tgz", + "integrity": "sha512-2y0Tuo6ZAT2Cz8V7DHulSlv1Bip3zbzeXyeur+uR25IRNYXKvI/P99Zl85Fbuu/zzYAZRLLlGTRe6/9IHofe/w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2445,6 +4940,24 @@ "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, + "node_modules/@react-email/components/node_modules/@react-email/render": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.1.2.tgz", + "integrity": "sha512-RnRehYN3v9gVlNMehHPHhyp2RQo7+pSkHDtXPvg3s0GbzM9SQMW4Qrf8GRNvtpLC4gsI+Wt0VatNRUFqjvevbw==", + "license": "MIT", + "dependencies": { + "html-to-text": "^9.0.5", + "prettier": "^3.5.3", + "react-promise-suspense": "^0.3.4" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" + } + }, "node_modules/@react-email/container": { "version": "0.0.15", "resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.15.tgz", @@ -2566,9 +5079,9 @@ } }, "node_modules/@react-email/render": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.1.2.tgz", - "integrity": "sha512-RnRehYN3v9gVlNMehHPHhyp2RQo7+pSkHDtXPvg3s0GbzM9SQMW4Qrf8GRNvtpLC4gsI+Wt0VatNRUFqjvevbw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.1.3.tgz", + "integrity": "sha512-TjjF1tdTmOqYEIWWg9wMx5q9JbQRbWmnG7owQbSGEHkNfc/c/vBu7hjfrki907lgQEAkYac9KPTyIjOKhvhJCg==", "license": "MIT", "dependencies": { "html-to-text": "^9.0.5", @@ -2669,6 +5182,738 @@ "url": "https://ko-fi.com/killymxi" } }, + "node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.6.0.tgz", + "integrity": "sha512-Pgvfb+TQ4wUNLyHzvgCP4aYZMh16y7GcfF59oirRHcgGgkH1e/s9C0nv/v3WP+Quymyr5je71HeFQCwh+44XLg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.4.tgz", + "integrity": "sha512-7XoWfZqWb/QoR/rAU4VSi0mWnO2vu9/ltS6JZ5ZSZv0eovLVfDfu0/AX4ub33RsJTOth3TiFWSHS5YdztvFnig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.4.tgz", + "integrity": "sha512-3fb/9SYaYqbpy/z/H3yIi0bYKyAa89y6xPmIqwr2vQiUT2St+avRt8UKwsWt9fEdEasc5d/V+QjrviRaX1JRFA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.2.tgz", + "integrity": "sha512-JGtambizrWP50xHgbzZI04IWU7LdI0nh/wGbqH3sJesYToMi2j/DcoElqyOcqEIG/D4tNyxgRuaqBXWE3zOFhQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.4.tgz", + "integrity": "sha512-RD6UwNZ5zISpOWPuhVgRz60GkSIp0dy1fuZmj4RYmqLVRtejFqQ16WmfYDdoSoAjlp1LX+FnZo+/hkdmyyGZ1w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.4.tgz", + "integrity": "sha512-UeJpOmLGhq1SLox79QWw/0n2PFX+oPRE1ZyRMxPIaFEfCqWaqpB7BU9C8kpPOGEhLF7AwEqfFbtwNxGy4ReENA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", + "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", + "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", + "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.13.tgz", + "integrity": "sha512-xg3EHV/Q5ZdAO5b0UiIMj3RIOCobuS40pBBODguUDVdko6YK6QIzCVRrHTogVuEKglBWqWenRnZ71iZnLL3ZAQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.6.0", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.14.tgz", + "integrity": "sha512-eoXaLlDGpKvdmvt+YBfRXE7HmIEtFF+DJCbTPwuLunP0YUnrydl+C4tS+vEM0+nyxXrX3PSUFqC+lP1+EHB1Tw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", + "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.5.tgz", + "integrity": "sha512-+lynZjGuUFJaMdDYSTMnP/uPBBXXukVfrJlP+1U/Dp5SFTEI++w6NMga8DjOENxecOF71V9Z2DllaVDYRnGlkg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.6.0", + "@smithy/middleware-endpoint": "^4.1.13", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.21.tgz", + "integrity": "sha512-wM0jhTytgXu3wzJoIqpbBAG5U6BwiubZ6QKzSbP7/VbmF1v96xlAbX2Am/mz0Zep0NLvLh84JT0tuZnk3wmYQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.21.tgz", + "integrity": "sha512-/F34zkoU0GzpUgLJydHY8Rxu9lBn8xQC/s/0M0U9lLBkYbA1htaAFjWYJzpzsbXPuri5D1H8gjp2jBum05qBrA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.5", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", + "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.0.6", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.6.tgz", + "integrity": "sha512-slcr1wdRbX7NFphXZOxtxRNA7hXAAtJAXJDE/wdoMAos27SIquVCKiSqfB6/28YzQ8FCsB5NKkhdM5gMADbqxg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -2748,6 +5993,23 @@ "@tailwindcss/oxide-win32-x64-msvc": "4.1.10" } }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.10.tgz", + "integrity": "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@tailwindcss/oxide-darwin-arm64": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.10.tgz", @@ -2765,6 +6027,189 @@ "node": ">= 10" } }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.10.tgz", + "integrity": "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.10.tgz", + "integrity": "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.10.tgz", + "integrity": "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.10.tgz", + "integrity": "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.10.tgz", + "integrity": "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.10.tgz", + "integrity": "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.10.tgz", + "integrity": "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.10.tgz", + "integrity": "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.10", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.10.tgz", + "integrity": "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.10.tgz", + "integrity": "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@tailwindcss/postcss": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.10.tgz", @@ -2812,6 +6257,16 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/better-sqlite3": { "version": "7.6.12", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.12.tgz", @@ -2936,9 +6391,9 @@ "license": "MIT" }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", - "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "dev": true, "license": "MIT", "dependencies": { @@ -2961,9 +6416,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.31.tgz", - "integrity": "sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==", + "version": "22.15.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.33.tgz", + "integrity": "sha512-wzoocdnnpSxZ+6CjW4ADCK1jVmd1S/J3ArNWfn8FDDQtRm8dkDg7TA+mvek2wNrfCgwuZxqEOiB9B1XCJ6+dbw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2980,6 +6435,18 @@ "@types/node": "*" } }, + "node_modules/@types/pg": { + "version": "8.15.4", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz", + "integrity": "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -3061,6 +6528,12 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -3089,16 +6562,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", - "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", + "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/type-utils": "8.34.0", - "@typescript-eslint/utils": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/type-utils": "8.35.0", + "@typescript-eslint/utils": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3112,7 +6585,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.34.0", + "@typescript-eslint/parser": "^8.35.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -3127,15 +6600,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", - "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", + "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4" }, "engines": { @@ -3151,13 +6624,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", - "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", + "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.34.0", - "@typescript-eslint/types": "^8.34.0", + "@typescript-eslint/tsconfig-utils": "^8.35.0", + "@typescript-eslint/types": "^8.35.0", "debug": "^4.3.4" }, "engines": { @@ -3172,13 +6645,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", - "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", + "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0" + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3189,9 +6662,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", - "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", + "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3205,13 +6678,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", - "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", + "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/utils": "8.35.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3228,9 +6701,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", - "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", + "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3241,15 +6714,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", - "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", + "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.34.0", - "@typescript-eslint/tsconfig-utils": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/project-service": "8.35.0", + "@typescript-eslint/tsconfig-utils": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3321,15 +6794,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", - "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", + "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0" + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3344,13 +6817,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", - "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", + "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.35.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3360,10 +6833,36 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.2.tgz", + "integrity": "sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.2.tgz", + "integrity": "sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.0.tgz", - "integrity": "sha512-nJ9z47kfFnCxN1z/oYZS7HSNsFh43y2asePzTEZpEvK7kGyuShSl3RRXnm/1QaqFL+iP+BjMwuB+DYUymOkA5A==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.2.tgz", + "integrity": "sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==", "cpu": [ "arm64" ], @@ -3373,6 +6872,217 @@ "darwin" ] }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.2.tgz", + "integrity": "sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.2.tgz", + "integrity": "sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.2.tgz", + "integrity": "sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.2.tgz", + "integrity": "sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.2.tgz", + "integrity": "sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.2.tgz", + "integrity": "sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.2.tgz", + "integrity": "sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.2.tgz", + "integrity": "sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.2.tgz", + "integrity": "sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.2.tgz", + "integrity": "sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.2.tgz", + "integrity": "sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.2.tgz", + "integrity": "sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.2.tgz", + "integrity": "sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz", + "integrity": "sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.2.tgz", + "integrity": "sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz", + "integrity": "sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3885,6 +7595,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -4021,9 +7737,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001722", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz", - "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==", + "version": "1.0.30001724", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz", + "integrity": "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==", "funding": [ { "type": "opencollective", @@ -5021,9 +8737,9 @@ } }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -5111,9 +8827,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5544,9 +9260,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "license": "MIT", "dependencies": { "debug": "^3.2.7" @@ -5570,29 +9286,29 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "license": "MIT", "dependencies": { "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", + "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", - "is-core-module": "^2.15.1", + "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", - "object.values": "^1.2.0", + "object.values": "^1.2.1", "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", + "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "engines": { @@ -5979,6 +9695,28 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -6259,6 +9997,13 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs-monkey": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", + "license": "Unlicense", + "optional": true + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -7607,6 +11352,195 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7798,6 +11732,29 @@ "node": ">= 0.6" } }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "license": "Unlicense", + "optional": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/memfs-browser": { + "version": "3.5.10302", + "resolved": "https://registry.npmjs.org/memfs-browser/-/memfs-browser-3.5.10302.tgz", + "integrity": "sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==", + "license": "Unlicense", + "optional": true, + "dependencies": { + "memfs": "3.5.3" + } + }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -8137,9 +12094,9 @@ } }, "node_modules/next-intl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.1.0.tgz", - "integrity": "sha512-JNJRjc7sdnfUxhZmGcvzDszZ60tQKrygV/VLsgzXhnJDxQPn1cN2rVpc53adA1SvBJwPK2O6Sc6b4gYSILjCzw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.3.1.tgz", + "integrity": "sha512-FylHpOoQw5MpOyJt4cw8pNEGba7r3jKDSqt112fmBqXVceGR5YncmqpxS5MvSHsWRwbjqpOV8OsZCIY/4f4HWg==", "funding": [ { "type": "individual", @@ -8150,7 +12107,7 @@ "dependencies": { "@formatjs/intl-localematcher": "^0.5.4", "negotiator": "^1.0.0", - "use-intl": "^4.1.0" + "use-intl": "^4.3.1" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", @@ -10949,12 +14906,12 @@ } }, "node_modules/openapi3-ts": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-4.4.0.tgz", - "integrity": "sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-4.5.0.tgz", + "integrity": "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==", "license": "MIT", "dependencies": { - "yaml": "^2.5.0" + "yaml": "^2.8.0" } }, "node_modules/optimist": { @@ -11086,6 +15043,26 @@ "@node-rs/bcrypt": "1.9.0" } }, + "node_modules/oslo/node_modules/@emnapi/core": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-0.45.0.tgz", + "integrity": "sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/oslo/node_modules/@emnapi/runtime": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", + "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/oslo/node_modules/@node-rs/argon2": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.7.0.tgz", @@ -11111,6 +15088,38 @@ "@node-rs/argon2-win32-x64-msvc": "1.7.0" } }, + "node_modules/oslo/node_modules/@node-rs/argon2-android-arm-eabi": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-1.7.0.tgz", + "integrity": "sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-android-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-1.7.0.tgz", + "integrity": "sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/oslo/node_modules/@node-rs/argon2-darwin-arm64": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-1.7.0.tgz", @@ -11127,6 +15136,195 @@ "node": ">= 10" } }, + "node_modules/oslo/node_modules/@node-rs/argon2-darwin-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-1.7.0.tgz", + "integrity": "sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-freebsd-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-1.7.0.tgz", + "integrity": "sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-linux-arm-gnueabihf": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-1.7.0.tgz", + "integrity": "sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-linux-arm64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-1.7.0.tgz", + "integrity": "sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-linux-arm64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-1.7.0.tgz", + "integrity": "sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-linux-x64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-1.7.0.tgz", + "integrity": "sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-wasm32-wasi": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-1.7.0.tgz", + "integrity": "sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^0.45.0", + "@emnapi/runtime": "^0.45.0", + "@tybys/wasm-util": "^0.8.1", + "memfs-browser": "^3.4.13000" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-win32-arm64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-1.7.0.tgz", + "integrity": "sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-win32-ia32-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-1.7.0.tgz", + "integrity": "sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@node-rs/argon2-win32-x64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-1.7.0.tgz", + "integrity": "sha512-9oq4ShyFakw8AG3mRls0AoCpxBFcimYx7+jvXeAf2OqKNO+mSA6eZ9z7KQeVCi0+SOEUYxMGf5UiGiDb9R6+9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/oslo/node_modules/@tybys/wasm-util": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.8.3.tgz", + "integrity": "sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -11280,22 +15478,22 @@ } }, "node_modules/pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", + "version": "8.16.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.2.tgz", + "integrity": "sha512-OtLWF0mKLmpxelOt9BqVq83QV6bTfsS0XLegIeAKqKjurRnRKie1Dc1iL89MugmSLhftxw6NNCyZhm1yQFLMEQ==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.9.0", - "pg-pool": "^3.10.0", - "pg-protocol": "^1.10.0", + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.2", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.2.5" + "pg-cloudflare": "^1.2.6" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -11307,16 +15505,16 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz", - "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.6.tgz", + "integrity": "sha512-uxmJAnmIgmYgnSFzgOf2cqGQBzwnRYcrEgXuFjJNEkpedEIPBSEzxY7ph4uA9k1mI+l/GR0HjPNS6FKNZe8SBQ==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.0.tgz", - "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", "license": "MIT" }, "node_modules/pg-int8": { @@ -11329,18 +15527,18 @@ } }, "node_modules/pg-pool": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.0.tgz", - "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", - "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.2.tgz", + "integrity": "sha512-Ci7jy8PbaWxfsck2dwZdERcDG2A0MG8JoQILs+uZNjABFuBuItAZCWUNz8sXRDMoui24rJw7WlXqgpMdBSN/vQ==", "license": "MIT" }, "node_modules/pg-types": { @@ -11409,9 +15607,9 @@ } }, "node_modules/postcss": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", - "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -11512,9 +15710,9 @@ } }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.0.tgz", + "integrity": "sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -11566,9 +15764,9 @@ "license": "MIT" }, "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -13058,6 +17256,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stripe": { + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-18.2.1.tgz", + "integrity": "sha512-GwB1B7WSwEBzW4dilgyJruUYhbGMscrwuyHsPUmSRKrGHZ5poSh2oU9XKdii5BFVJzXHn35geRvGJ6R8bYcp8w==", + "license": "MIT", + "dependencies": { + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + }, + "peerDependencies": { + "@types/node": ">=12.x.x" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -13106,9 +17336,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.24.1", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.24.1.tgz", - "integrity": "sha512-ITeWc7CCAfK53u8jnV39UNqStQZjSt+bVYtJHsOEL3vVj/WV9/8HmsF8Ej4oD8r+Xk1HpWyeW/t59r1QNeAcUQ==", + "version": "5.25.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.25.2.tgz", + "integrity": "sha512-V4JyoygUe5nCbn7bAD0fVKSC0yNcL3ROIQtGC7M0NATKuyosCSmMU6T0yDZIIuGpSxjsjZh/D2Ejb8lnF2jjxw==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -13548,15 +17778,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", - "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz", + "integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.34.0", - "@typescript-eslint/parser": "8.34.0", - "@typescript-eslint/utils": "8.34.0" + "@typescript-eslint/eslint-plugin": "8.35.0", + "@typescript-eslint/parser": "8.35.0", + "@typescript-eslint/utils": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -13605,37 +17835,37 @@ } }, "node_modules/unrs-resolver": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.0.tgz", - "integrity": "sha512-wqaRu4UnzBD2ABTC1kLfBjAqIDZ5YUTr/MLGa7By47JV1bJDSW7jq/ZSLigB7enLe7ubNaJhtnBXgrc/50cEhg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.2.tgz", + "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.2.2" + "napi-postinstall": "^0.2.4" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.9.0", - "@unrs/resolver-binding-android-arm64": "1.9.0", - "@unrs/resolver-binding-darwin-arm64": "1.9.0", - "@unrs/resolver-binding-darwin-x64": "1.9.0", - "@unrs/resolver-binding-freebsd-x64": "1.9.0", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.0", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.0", - "@unrs/resolver-binding-linux-arm64-gnu": "1.9.0", - "@unrs/resolver-binding-linux-arm64-musl": "1.9.0", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.0", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.0", - "@unrs/resolver-binding-linux-riscv64-musl": "1.9.0", - "@unrs/resolver-binding-linux-s390x-gnu": "1.9.0", - "@unrs/resolver-binding-linux-x64-gnu": "1.9.0", - "@unrs/resolver-binding-linux-x64-musl": "1.9.0", - "@unrs/resolver-binding-wasm32-wasi": "1.9.0", - "@unrs/resolver-binding-win32-arm64-msvc": "1.9.0", - "@unrs/resolver-binding-win32-ia32-msvc": "1.9.0", - "@unrs/resolver-binding-win32-x64-msvc": "1.9.0" + "@unrs/resolver-binding-android-arm-eabi": "1.9.2", + "@unrs/resolver-binding-android-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-x64": "1.9.2", + "@unrs/resolver-binding-freebsd-x64": "1.9.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.9.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.9.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-musl": "1.9.2", + "@unrs/resolver-binding-wasm32-wasi": "1.9.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.9.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.9.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.9.2" } }, "node_modules/uri-js": { @@ -13669,9 +17899,9 @@ } }, "node_modules/use-intl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.1.0.tgz", - "integrity": "sha512-mQvDYFvoGn+bm/PWvlQOtluKCknsQ5a9F1Cj0hMfBjMBVTwnOqLPd6srhjvVdEQEQFVyHM1PfyifKqKYb11M9Q==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.3.1.tgz", + "integrity": "sha512-8Xn5RXzeHZhWqqZimi1wi2pKFqm0NxRUOB41k1QdjbPX+ysoeLW3Ey+fi603D/e5EGb0fYw8WzjgtUagJdlIvg==", "license": "MIT", "dependencies": { "@formatjs/fast-memoize": "^2.2.0", diff --git a/package.json b/package.json index 2a1f03f3..76e26ec3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "@asteasolutions/zod-to-openapi": "^7.3.2", + "@aws-sdk/client-s3": "3.837.0", "@hookform/resolvers": "3.9.1", "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "1.0.1", @@ -101,6 +102,7 @@ "react-icons": "^5.5.0", "rebuild": "0.1.2", "semver": "7.7.2", + "stripe": "18.2.1", "swagger-ui-express": "^5.0.1", "tailwind-merge": "2.6.0", "tw-animate-css": "^1.3.3", @@ -127,6 +129,7 @@ "@types/jsonwebtoken": "^9.0.9", "@types/node": "^22", "@types/nodemailer": "6.4.17", + "@types/pg": "8.15.4", "@types/react": "19.1.7", "@types/react-dom": "19.1.6", "@types/semver": "7.7.0", diff --git a/server/apiServer.ts b/server/apiServer.ts index ace27e9b..8ac74b73 100644 --- a/server/apiServer.ts +++ b/server/apiServer.ts @@ -6,7 +6,8 @@ import logger from "@server/logger"; import { errorHandlerMiddleware, notFoundMiddleware, - rateLimitMiddleware + rateLimitMiddleware, + requestTimeoutMiddleware } from "@server/middlewares"; import { authenticated, unauthenticated } from "@server/routers/external"; import { router as wsRouter, handleWSUpgrade } from "@server/routers/ws"; @@ -19,6 +20,7 @@ const externalPort = config.getRawConfig().server.external_port; export function createApiServer() { const apiServer = express(); + const prefix = `/api/v1`; const trustProxy = config.getRawConfig().server.trust_proxy; if (trustProxy) { @@ -54,6 +56,9 @@ export function createApiServer() { apiServer.use(cookieParser()); apiServer.use(express.json()); + // Add request timeout middleware + apiServer.use(requestTimeoutMiddleware(60000)); // 60 second timeout + if (!dev) { apiServer.use( rateLimitMiddleware({ @@ -66,7 +71,6 @@ export function createApiServer() { } // API routes - const prefix = `/api/v1`; apiServer.use(logIncomingMiddleware); apiServer.use(prefix, unauthenticated); apiServer.use(prefix, authenticated); diff --git a/server/auth/actions.ts b/server/auth/actions.ts index f5a7f5da..95c3bdcb 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -90,7 +90,10 @@ export enum ActionsEnum { setApiKeyOrgs = "setApiKeyOrgs", listApiKeyActions = "listApiKeyActions", listApiKeys = "listApiKeys", - getApiKey = "getApiKey" + getApiKey = "getApiKey", + createOrgDomain = "createOrgDomain", + deleteOrgDomain = "deleteOrgDomain", + restartOrgDomain = "restartOrgDomain" } export async function checkUserActionPermission( diff --git a/server/auth/limits.ts b/server/auth/limits.ts deleted file mode 100644 index 5d0b14e4..00000000 --- a/server/auth/limits.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { db } from '@server/db'; -import { limitsTable } from '@server/db'; -import { and, eq } from 'drizzle-orm'; -import createHttpError from 'http-errors'; -import HttpCode from '@server/types/HttpCode'; - -interface CheckLimitOptions { - orgId: string; - limitName: string; - currentValue: number; - increment?: number; -} - -export async function checkOrgLimit({ orgId, limitName, currentValue, increment = 0 }: CheckLimitOptions): Promise { - try { - const limit = await db.select() - .from(limitsTable) - .where( - and( - eq(limitsTable.orgId, orgId), - eq(limitsTable.name, limitName) - ) - ) - .limit(1); - - if (limit.length === 0) { - throw createHttpError(HttpCode.NOT_FOUND, `Limit "${limitName}" not found for organization`); - } - - const limitValue = limit[0].value; - - // Check if the current value plus the increment is within the limit - return (currentValue + increment) <= limitValue; - } catch (error) { - if (error instanceof Error) { - throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Error checking limit: ${error.message}`); - } - throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Unknown error occurred while checking limit'); - } -} diff --git a/server/db/names.ts b/server/db/names.ts index 56d62373..41f4c170 100644 --- a/server/db/names.ts +++ b/server/db/names.ts @@ -59,7 +59,7 @@ export async function getUniqueExitNodeEndpointName(): Promise { export function generateName(): string { - return ( + const name = ( names.descriptors[ Math.floor(Math.random() * names.descriptors.length) ] + @@ -68,4 +68,7 @@ export function generateName(): string { ) .toLowerCase() .replace(/\s/g, "-"); + + // clean out any non-alphanumeric characters except for dashes + return name.replace(/[^a-z0-9-]/g, ""); } diff --git a/server/db/pg/driver.ts b/server/db/pg/driver.ts index 116e4610..9625867d 100644 --- a/server/db/pg/driver.ts +++ b/server/db/pg/driver.ts @@ -1,4 +1,5 @@ import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres"; +import { Pool } from "pg"; import { readConfigFile } from "@server/lib/readConfigFile"; import { withReplicas } from "drizzle-orm/pg-core"; @@ -20,19 +21,31 @@ function createDb() { ); } - const primary = DrizzlePostgres(connectionString); + // Create connection pools instead of individual connections + const primaryPool = new Pool({ + connectionString, + max: 20, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, + }); + const replicas = []; if (!replicaConnections.length) { - replicas.push(primary); + replicas.push(DrizzlePostgres(primaryPool)); } else { for (const conn of replicaConnections) { - const replica = DrizzlePostgres(conn.connection_string); - replicas.push(replica); + const replicaPool = new Pool({ + connectionString: conn.connection_string, + max: 10, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, + }); + replicas.push(DrizzlePostgres(replicaPool)); } } - return withReplicas(primary, replicas as any); + return withReplicas(DrizzlePostgres(primaryPool), replicas as any); } export const db = createDb(); diff --git a/server/db/pg/index.ts b/server/db/pg/index.ts index 9ad4678c..4829c04c 100644 --- a/server/db/pg/index.ts +++ b/server/db/pg/index.ts @@ -1,2 +1,2 @@ export * from "./driver"; -export * from "./schema"; +export * from "./schema"; \ No newline at end of file diff --git a/server/db/pg/schema.ts b/server/db/pg/schema.ts index 1e4a99c7..c7cea3fb 100644 --- a/server/db/pg/schema.ts +++ b/server/db/pg/schema.ts @@ -14,6 +14,9 @@ export const domains = pgTable("domains", { baseDomain: varchar("baseDomain").notNull(), configManaged: boolean("configManaged").notNull().default(false), type: varchar("type"), // "ns", "cname", "a" + verified: boolean("verified").notNull().default(false), + failed: boolean("failed").notNull().default(false), + tries: integer("tries").notNull().default(0) }); export const orgs = pgTable("orgs", { @@ -44,9 +47,9 @@ export const sites = pgTable("sites", { }), name: varchar("name").notNull(), pubKey: varchar("pubKey"), - subnet: varchar("subnet").notNull(), - megabytesIn: real("bytesIn"), - megabytesOut: real("bytesOut"), + subnet: varchar("subnet"), + megabytesIn: real("bytesIn").default(0), + megabytesOut: real("bytesOut").default(0), lastBandwidthUpdate: varchar("lastBandwidthUpdate"), type: varchar("type").notNull(), // "newt" or "wireguard" online: boolean("online").notNull().default(false), @@ -282,18 +285,6 @@ export const userResources = pgTable("userResources", { .references(() => resources.resourceId, { onDelete: "cascade" }) }); -export const limitsTable = pgTable("limits", { - limitId: serial("limitId").primaryKey(), - orgId: varchar("orgId") - .references(() => orgs.orgId, { - onDelete: "cascade" - }) - .notNull(), - name: varchar("name").notNull(), - value: bigint("value", { mode: "number" }).notNull(), - description: varchar("description") -}); - export const userInvites = pgTable("userInvites", { inviteId: varchar("inviteId").primaryKey(), orgId: varchar("orgId") @@ -520,7 +511,8 @@ export const clients = pgTable("clients", { type: varchar("type").notNull(), // "olm" online: boolean("online").notNull().default(false), endpoint: varchar("endpoint"), - lastHolePunch: integer("lastHolePunch") + lastHolePunch: integer("lastHolePunch"), + maxConnections: integer("maxConnections") }); export const clientSites = pgTable("clientSites", { @@ -590,7 +582,6 @@ export type RoleSite = InferSelectModel; export type UserSite = InferSelectModel; export type RoleResource = InferSelectModel; export type UserResource = InferSelectModel; -export type Limit = InferSelectModel; export type UserInvite = InferSelectModel; export type UserOrg = InferSelectModel; export type ResourceSession = InferSelectModel; @@ -613,3 +604,4 @@ export type Olm = InferSelectModel; export type OlmSession = InferSelectModel; export type UserClient = InferSelectModel; export type RoleClient = InferSelectModel; +export type OrgDomains = InferSelectModel; \ No newline at end of file diff --git a/server/db/redis.ts b/server/db/redis.ts index e693c205..e521d720 100644 --- a/server/db/redis.ts +++ b/server/db/redis.ts @@ -3,30 +3,25 @@ import logger from "@server/logger"; import config from "@server/lib/config"; class RedisManager { - private static instance: RedisManager; public client: Redis | null = null; private subscriber: Redis | null = null; private publisher: Redis | null = null; private isEnabled: boolean = false; + private isHealthy: boolean = true; + private lastHealthCheck: number = 0; + private healthCheckInterval: number = 30000; // 30 seconds private subscribers: Map< string, Set<(channel: string, message: string) => void> > = new Map(); - private constructor() { + constructor() { this.isEnabled = config.getRawConfig().flags?.enable_redis || false; if (this.isEnabled) { this.initializeClients(); } } - public static getInstance(): RedisManager { - if (!RedisManager.instance) { - RedisManager.instance = new RedisManager(); - } - return RedisManager.instance; - } - private getRedisConfig(): RedisOptions { const redisConfig = config.getRawConfig().redis!; const opts: RedisOptions = { @@ -34,38 +29,78 @@ class RedisManager { port: redisConfig.port!, password: redisConfig.password, db: redisConfig.db, - tls: { - rejectUnauthorized: - redisConfig.tls?.reject_unauthorized || false - } + // tls: { + // rejectUnauthorized: + // redisConfig.tls?.reject_unauthorized || false + // } }; return opts; } + // Add reconnection logic in initializeClients private initializeClients(): void { const config = this.getRedisConfig(); try { - // Main client for general operations - this.client = new Redis(config); + this.client = new Redis({ + ...config, + enableReadyCheck: false, + maxRetriesPerRequest: 3, + keepAlive: 30000, + connectTimeout: 10000, // 10 seconds + commandTimeout: 5000, // 5 seconds + }); - // Dedicated publisher client - this.publisher = new Redis(config); + this.publisher = new Redis({ + ...config, + enableReadyCheck: false, + maxRetriesPerRequest: 3, + keepAlive: 30000, + connectTimeout: 10000, // 10 seconds + commandTimeout: 5000, // 5 seconds + }); - // Dedicated subscriber client - this.subscriber = new Redis(config); + this.subscriber = new Redis({ + ...config, + enableReadyCheck: false, + maxRetriesPerRequest: 3, + keepAlive: 30000, + connectTimeout: 10000, // 10 seconds + commandTimeout: 5000, // 5 seconds + }); - // Set up error handlers + // Add reconnection handlers this.client.on("error", (err) => { logger.error("Redis client error:", err); + this.isHealthy = false; + }); + + this.client.on("reconnecting", () => { + logger.info("Redis client reconnecting..."); + this.isHealthy = false; + }); + + this.client.on("ready", () => { + logger.info("Redis client ready"); + this.isHealthy = true; }); this.publisher.on("error", (err) => { logger.error("Redis publisher error:", err); + this.isHealthy = false; + }); + + this.publisher.on("ready", () => { + logger.info("Redis publisher ready"); }); this.subscriber.on("error", (err) => { logger.error("Redis subscriber error:", err); + this.isHealthy = false; + }); + + this.subscriber.on("ready", () => { + logger.info("Redis subscriber ready"); }); // Set up connection handlers @@ -102,18 +137,65 @@ class RedisManager { ); logger.info("Redis clients initialized successfully"); + + // Start periodic health monitoring + this.startHealthMonitoring(); } catch (error) { logger.error("Failed to initialize Redis clients:", error); this.isEnabled = false; } } - public isRedisEnabled(): boolean { - return this.isEnabled && this.client !== null; + private startHealthMonitoring(): void { + if (!this.isEnabled) return; + + // Check health every 30 seconds + setInterval(async () => { + try { + await this.checkRedisHealth(); + } catch (error) { + logger.error("Error during Redis health monitoring:", error); + } + }, this.healthCheckInterval); } - public getClient(): Redis | null { - return this.client; + public isRedisEnabled(): boolean { + return this.isEnabled && this.client !== null && this.isHealthy; + } + + private async checkRedisHealth(): Promise { + const now = Date.now(); + + // Only check health every 30 seconds + if (now - this.lastHealthCheck < this.healthCheckInterval) { + return this.isHealthy; + } + + this.lastHealthCheck = now; + + if (!this.client) { + this.isHealthy = false; + return false; + } + + try { + await Promise.race([ + this.client.ping(), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Health check timeout')), 2000) + ) + ]); + this.isHealthy = true; + return true; + } catch (error) { + logger.error("Redis health check failed:", error); + this.isHealthy = false; + return false; + } + } + + public getClient(): Redis { + return this.client!; } public async set( @@ -247,11 +329,25 @@ class RedisManager { public async publish(channel: string, message: string): Promise { if (!this.isRedisEnabled() || !this.publisher) return false; + // Quick health check before attempting to publish + const isHealthy = await this.checkRedisHealth(); + if (!isHealthy) { + logger.warn("Skipping Redis publish due to unhealthy connection"); + return false; + } + try { - await this.publisher.publish(channel, message); + // Add timeout to prevent hanging + await Promise.race([ + this.publisher.publish(channel, message), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Redis publish timeout')), 3000) + ) + ]); return true; } catch (error) { logger.error("Redis PUBLISH error:", error); + this.isHealthy = false; // Mark as unhealthy on error return false; } } @@ -267,13 +363,19 @@ class RedisManager { if (!this.subscribers.has(channel)) { this.subscribers.set(channel, new Set()); // Only subscribe to the channel if it's the first subscriber - await this.subscriber.subscribe(channel); + await Promise.race([ + this.subscriber.subscribe(channel), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Redis subscribe timeout')), 5000) + ) + ]); } this.subscribers.get(channel)!.add(callback); return true; } catch (error) { logger.error("Redis SUBSCRIBE error:", error); + this.isHealthy = false; return false; } } @@ -330,5 +432,6 @@ class RedisManager { } } -export const redisManager = RedisManager.getInstance(); +export const redisManager = new RedisManager(); +export const redis = redisManager.getClient(); export default redisManager; diff --git a/server/db/sqlite/schema.ts b/server/db/sqlite/schema.ts index 98f2ffe8..173df470 100644 --- a/server/db/sqlite/schema.ts +++ b/server/db/sqlite/schema.ts @@ -7,7 +7,7 @@ export const domains = sqliteTable("domains", { configManaged: integer("configManaged", { mode: "boolean" }) .notNull() .default(false), - type: text("type"), // "ns", "cname", "a" + type: text("type") // "ns", "cname", "a" }); export const orgs = sqliteTable("orgs", { @@ -16,6 +16,15 @@ export const orgs = sqliteTable("orgs", { subnet: text("subnet").notNull(), }); +export const userDomains = sqliteTable("userDomains", { + userId: text("userId") + .notNull() + .references(() => users.userId, { onDelete: "cascade" }), + domainId: text("domainId") + .notNull() + .references(() => domains.domainId, { onDelete: "cascade" }) +}); + export const orgDomains = sqliteTable("orgDomains", { orgId: text("orgId") .notNull() @@ -38,9 +47,9 @@ export const sites = sqliteTable("sites", { }), name: text("name").notNull(), pubKey: text("pubKey"), - subnet: text("subnet").notNull(), - megabytesIn: integer("bytesIn"), - megabytesOut: integer("bytesOut"), + subnet: text("subnet"), + megabytesIn: integer("bytesIn").default(0), + megabytesOut: integer("bytesOut").default(0), lastBandwidthUpdate: text("lastBandwidthUpdate"), type: text("type").notNull(), // "newt" or "wireguard" online: integer("online", { mode: "boolean" }).notNull().default(false), @@ -48,7 +57,7 @@ export const sites = sqliteTable("sites", { // exit node stuff that is how to connect to the site when it has a wg server address: text("address"), // this is the address of the wireguard interface in newt endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config - publicKey: text("pubicKey"), // TODO: Fix typo in publicKey + publicKey: text("publicKey"), // TODO: Fix typo in publicKey lastHolePunch: integer("lastHolePunch"), listenPort: integer("listenPort"), dockerSocketEnabled: integer("dockerSocketEnabled", { mode: "boolean" }) @@ -626,13 +635,14 @@ export type ResourceAccessToken = InferSelectModel; export type ResourceWhitelist = InferSelectModel; export type VersionMigration = InferSelectModel; export type ResourceRule = InferSelectModel; +export type Domain = InferSelectModel; export type Client = InferSelectModel; export type ClientSite = InferSelectModel; export type RoleClient = InferSelectModel; export type UserClient = InferSelectModel; -export type Domain = InferSelectModel; export type SupporterKey = InferSelectModel; export type Idp = InferSelectModel; export type ApiKey = InferSelectModel; export type ApiKeyAction = InferSelectModel; export type ApiKeyOrg = InferSelectModel; +export type OrgDomains = InferSelectModel; \ No newline at end of file diff --git a/server/emails/sendEmail.ts b/server/emails/sendEmail.ts index d7a59608..4c5586f1 100644 --- a/server/emails/sendEmail.ts +++ b/server/emails/sendEmail.ts @@ -2,6 +2,7 @@ import { render } from "@react-email/render"; import { ReactElement } from "react"; import emailClient from "@server/emails"; import logger from "@server/logger"; +import config from "@server/lib/config"; export async function sendEmail( template: ReactElement, @@ -24,9 +25,11 @@ export async function sendEmail( const emailHtml = await render(template); + const appName = "Fossorial - Pangolin"; + await emailClient.sendMail({ from: { - name: opts.name || "Pangolin", + name: opts.name || appName, address: opts.from, }, to: opts.to, diff --git a/server/emails/templates/NotifyResetPassword.tsx b/server/emails/templates/NotifyResetPassword.tsx index aaa1cbdd..66ea2430 100644 --- a/server/emails/templates/NotifyResetPassword.tsx +++ b/server/emails/templates/NotifyResetPassword.tsx @@ -1,11 +1,5 @@ -import { - Body, - Head, - Html, - Preview, - Tailwind -} from "@react-email/components"; -import * as React from "react"; +import React from "react"; +import { Body, Head, Html, Preview, Tailwind } from "@react-email/components"; import { themeColors } from "./lib/theme"; import { EmailContainer, @@ -22,29 +16,29 @@ interface Props { } export const ConfirmPasswordReset = ({ email }: Props) => { - const previewText = `Your password has been reset`; + const previewText = `Your password has been successfully reset.`; return ( {previewText} - + - Password Reset Confirmation + {/* Password Successfully Reset */} - Hi {email || "there"}, + Hi there, - This email confirms that your password has just been - reset. If you made this change, no further action is - required. + Your password has been successfully reset. You can + now sign in to your account using your new password. - Thank you for keeping your account secure. + If you didn't make this change, please contact our + support team immediately to secure your account. diff --git a/server/emails/templates/ResetPasswordCode.tsx b/server/emails/templates/ResetPasswordCode.tsx index 1a79527b..df14b8be 100644 --- a/server/emails/templates/ResetPasswordCode.tsx +++ b/server/emails/templates/ResetPasswordCode.tsx @@ -1,11 +1,5 @@ -import { - Body, - Head, - Html, - Preview, - Tailwind -} from "@react-email/components"; -import * as React from "react"; +import React from "react"; +import { Body, Head, Html, Preview, Tailwind } from "@react-email/components"; import { themeColors } from "./lib/theme"; import { EmailContainer, @@ -18,6 +12,7 @@ import { EmailText } from "./components/Email"; import CopyCodeBox from "./components/CopyCodeBox"; +import ButtonLink from "./components/ButtonLink"; interface Props { email: string; @@ -26,37 +21,39 @@ interface Props { } export const ResetPasswordCode = ({ email, code, link }: Props) => { - const previewText = `Your password reset code is ${code}`; + const previewText = `Reset your password with code: ${code}`; return ( {previewText} - + - Password Reset Request + {/* Reset Your Password */} - Hi {email || "there"}, + Hi there, - You’ve requested to reset your password. Please{" "} - - click here - {" "} - and follow the instructions to reset your password, - or manually enter the following code: + You've requested to reset your password. Click the + button below to reset your password, or use the + verification code provided if prompted. + + Reset Password + + - If you didn’t request this, you can safely ignore - this email. + This reset code will expire in 2 hours. If you + didn't request a password reset, you can safely + ignore this email. diff --git a/server/emails/templates/ResourceOTPCode.tsx b/server/emails/templates/ResourceOTPCode.tsx index 086dc444..4f68d9df 100644 --- a/server/emails/templates/ResourceOTPCode.tsx +++ b/server/emails/templates/ResourceOTPCode.tsx @@ -1,11 +1,5 @@ -import { - Body, - Head, - Html, - Preview, - Tailwind -} from "@react-email/components"; -import * as React from "react"; +import React from "react"; +import { Body, Head, Html, Preview, Tailwind } from "@react-email/components"; import { EmailContainer, EmailLetterHead, @@ -32,34 +26,40 @@ export const ResourceOTPCode = ({ orgName: organizationName, otp }: ResourceOTPCodeProps) => { - const previewText = `Your one-time password for ${resourceName} is ${otp}`; + const previewText = `Your access code for ${resourceName}: ${otp}`; return ( {previewText} - + - - Your One-Time Code for {resourceName} - + {/* */} + {/* Access Code for {resourceName} */} + {/* */} - Hi {email || "there"}, + Hi there, - You’ve requested a one-time password to access{" "} + You've requested access to{" "} {resourceName} in{" "} - {organizationName}. Use the code - below to complete your authentication: + {organizationName}. Use the + verification code below to complete your + authentication. + + This code will expire in 15 minutes. If you didn't + request this code, please ignore this email. + + diff --git a/server/emails/templates/SendInviteLink.tsx b/server/emails/templates/SendInviteLink.tsx index ed3c7b53..c859d3d7 100644 --- a/server/emails/templates/SendInviteLink.tsx +++ b/server/emails/templates/SendInviteLink.tsx @@ -1,11 +1,5 @@ -import { - Body, - Head, - Html, - Preview, - Tailwind, -} from "@react-email/components"; -import * as React from "react"; +import React from "react"; +import { Body, Head, Html, Preview, Tailwind } from "@react-email/components"; import { themeColors } from "./lib/theme"; import { EmailContainer, @@ -41,35 +35,44 @@ export const SendInviteLink = ({ {previewText} - + - Invited to Join {orgName} + {/* */} + {/* You're Invited to Join {orgName} */} + {/* */} - Hi {email || "there"}, + Hi there, - You’ve been invited to join the organization{" "} + You've been invited to join{" "} {orgName} - {inviterName ? ` by ${inviterName}.` : "."} Please - access the link below to accept the invite. - - - - This invite will expire in{" "} - - {expiresInDays}{" "} - {expiresInDays === "1" ? "day" : "days"}. - + {inviterName ? ` by ${inviterName}` : ""}. Click the + button below to accept your invitation and get + started. - Accept Invite to {orgName} + Accept Invitation + {/* */} + {/* If you're having trouble clicking the button, copy */} + {/* and paste the URL below into your web browser: */} + {/*
*/} + {/* {inviteLink} */} + {/*
*/} + + + This invite expires in {expiresInDays}{" "} + {expiresInDays === "1" ? "day" : "days"}. If the + link has expired, please contact the owner of the + organization to request a new invitation. + + diff --git a/server/emails/templates/TwoFactorAuthNotification.tsx b/server/emails/templates/TwoFactorAuthNotification.tsx index 8993a3bd..3261023e 100644 --- a/server/emails/templates/TwoFactorAuthNotification.tsx +++ b/server/emails/templates/TwoFactorAuthNotification.tsx @@ -1,11 +1,5 @@ -import { - Body, - Head, - Html, - Preview, - Tailwind -} from "@react-email/components"; -import * as React from "react"; +import React from "react"; +import { Body, Head, Html, Preview, Tailwind } from "@react-email/components"; import { themeColors } from "./lib/theme"; import { EmailContainer, @@ -23,44 +17,52 @@ interface Props { } export const TwoFactorAuthNotification = ({ email, enabled }: Props) => { - const previewText = `Two-Factor Authentication has been ${enabled ? "enabled" : "disabled"}`; + const previewText = `Two-Factor Authentication ${enabled ? "enabled" : "disabled"} for your account`; return ( {previewText} - + - - Two-Factor Authentication{" "} - {enabled ? "Enabled" : "Disabled"} - + {/* */} + {/* Security Update: 2FA{" "} */} + {/* {enabled ? "Enabled" : "Disabled"} */} + {/* */} - Hi {email || "there"}, + Hi there, - This email confirms that Two-Factor Authentication - has been successfully{" "} - {enabled ? "enabled" : "disabled"} on your account. + Two-factor authentication has been successfully{" "} + {enabled ? "enabled" : "disabled"}{" "} + on your account. {enabled ? ( - - With Two-Factor Authentication enabled, your - account is now more secure. Please ensure you - keep your authentication method safe. - + <> + + Your account is now protected with an + additional layer of security. Keep your + authentication method safe and accessible. + + ) : ( - - With Two-Factor Authentication disabled, your - account may be less secure. We recommend - enabling it to protect your account. - + <> + + We recommend re-enabling two-factor + authentication to keep your account secure. + + )} + + If you didn't make this change, please contact our + support team immediately. + + diff --git a/server/emails/templates/VerifyEmailCode.tsx b/server/emails/templates/VerifyEmailCode.tsx index ad0ef053..6a361648 100644 --- a/server/emails/templates/VerifyEmailCode.tsx +++ b/server/emails/templates/VerifyEmailCode.tsx @@ -1,5 +1,5 @@ +import React from "react"; import { Body, Head, Html, Preview, Tailwind } from "@react-email/components"; -import * as React from "react"; import { themeColors } from "./lib/theme"; import { EmailContainer, @@ -24,25 +24,24 @@ export const VerifyEmail = ({ verificationCode, verifyLink }: VerifyEmailProps) => { - const previewText = `Your verification code is ${verificationCode}`; + const previewText = `Verify your email with code: ${verificationCode}`; return ( {previewText} - + - Please Verify Your Email + {/* Verify Your Email Address */} - Hi {username || "there"}, + Hi there, - You’ve requested to verify your email. Please use - the code below to complete the verification process - upon logging in. + Welcome! To complete your account setup, please + verify your email address using the code below. @@ -50,7 +49,8 @@ export const VerifyEmail = ({ - If you didn’t request this, you can safely ignore + This verification code will expire in 15 minutes. If + you didn't create an account, you can safely ignore this email. diff --git a/server/emails/templates/WelcomeQuickStart.tsx b/server/emails/templates/WelcomeQuickStart.tsx new file mode 100644 index 00000000..caebff06 --- /dev/null +++ b/server/emails/templates/WelcomeQuickStart.tsx @@ -0,0 +1,131 @@ +import React from "react"; +import { Body, Head, Html, Preview, Tailwind } from "@react-email/components"; +import { themeColors } from "./lib/theme"; +import { + EmailContainer, + EmailFooter, + EmailGreeting, + EmailHeading, + EmailLetterHead, + EmailSection, + EmailSignature, + EmailText, + EmailInfoSection +} from "./components/Email"; +import ButtonLink from "./components/ButtonLink"; +import CopyCodeBox from "./components/CopyCodeBox"; + +interface WelcomeQuickStartProps { + username?: string; + link: string; + fallbackLink: string; + resourceMethod: string; + resourceHostname: string; + resourcePort: string | number; + resourceUrl: string; + cliCommand: string; +} + +export const WelcomeQuickStart = ({ + username, + link, + fallbackLink, + resourceMethod, + resourceHostname, + resourcePort, + resourceUrl, + cliCommand +}: WelcomeQuickStartProps) => { + const previewText = "Welcome! Here's what to do next"; + + return ( + + + {previewText} + + + + + + Hi there, + + + Thank you for trying out Pangolin! We're excited to + have you on board. + + + + To continue to configure your site, resources, and + other features, complete your account setup to + access the full dashboard. + + + + + View Your Dashboard + + {/*

*/} + {/* If the button above doesn't work, you can also */} + {/* use this{" "} */} + {/* */} + {/* link */} + {/* */} + {/* . */} + {/*

*/} +
+ + +
+ Connect your site using Newt +
+
+
+ + {cliCommand} + +
+

+ To learn how to use Newt, including more + installation methods, visit the{" "} + + docs + + . +

+
+
+ + + {resourceUrl} + + ) + } + ]} + /> + + + + +
+ +
+ + ); +}; + +export default WelcomeQuickStart; diff --git a/server/emails/templates/components/ButtonLink.tsx b/server/emails/templates/components/ButtonLink.tsx index e32e1810..9086bd47 100644 --- a/server/emails/templates/components/ButtonLink.tsx +++ b/server/emails/templates/components/ButtonLink.tsx @@ -12,7 +12,11 @@ export default function ButtonLink({ return ( {children} diff --git a/server/emails/templates/components/CopyCodeBox.tsx b/server/emails/templates/components/CopyCodeBox.tsx index ef48b383..3e4d1d08 100644 --- a/server/emails/templates/components/CopyCodeBox.tsx +++ b/server/emails/templates/components/CopyCodeBox.tsx @@ -2,10 +2,15 @@ import React from "react"; export default function CopyCodeBox({ text }: { text: string }) { return ( -
- - {text} - +
+
+ + {text} + +
+

+ Copy and paste this code when prompted +

); } diff --git a/server/emails/templates/components/Email.tsx b/server/emails/templates/components/Email.tsx index c73e4c85..05912622 100644 --- a/server/emails/templates/components/Email.tsx +++ b/server/emails/templates/components/Email.tsx @@ -1,47 +1,26 @@ -import { Container } from "@react-email/components"; import React from "react"; +import { Container, Img } from "@react-email/components"; // EmailContainer: Wraps the entire email layout export function EmailContainer({ children }: { children: React.ReactNode }) { return ( - + {children} ); } -// EmailLetterHead: For branding or logo at the top +// EmailLetterHead: For branding with logo on dark background export function EmailLetterHead() { return ( -
- - - - - -
- Pangolin - - {new Date().getFullYear()} -
+
+ Fossorial
); } @@ -49,14 +28,22 @@ export function EmailLetterHead() { // EmailHeading: For the primary message or headline export function EmailHeading({ children }: { children: React.ReactNode }) { return ( -

- {children} -

+
+

+ {children} +

+
); } export function EmailGreeting({ children }: { children: React.ReactNode }) { - return

{children}

; + return ( +
+

+ {children} +

+
+ ); } // EmailText: For general text content @@ -68,9 +55,13 @@ export function EmailText({ className?: string; }) { return ( -

- {children} -

+
+

+ {children} +

+
); } @@ -82,20 +73,70 @@ export function EmailSection({ children: React.ReactNode; className?: string; }) { - return
{children}
; + return ( +
{children}
+ ); } // EmailFooter: For closing or signature export function EmailFooter({ children }: { children: React.ReactNode }) { - return
{children}
; + return ( +
+ {children} +

+ For any questions or support, please contact us at: +
+ support@fossorial.io +

+

+ © {new Date().getFullYear()} Fossorial, Inc. All rights + reserved. +

+
+ ); } export function EmailSignature() { return ( -

- Best regards, -
- Fossorial -

+
+

+ Best regards, +
+ The Fossorial Team +

+
+ ); +} + +// EmailInfoSection: For structured key-value info (like resource details) +export function EmailInfoSection({ + title, + items +}: { + title?: string; + items: { label: string; value: React.ReactNode }[]; +}) { + return ( +
+ {title && ( +
+ {title} +
+ )} + + + {items.map((item, idx) => ( + + + + + ))} + +
+ {item.label} + + {item.value} +
+
); } diff --git a/server/emails/templates/lib/theme.ts b/server/emails/templates/lib/theme.ts index ada77fd2..a10ff77a 100644 --- a/server/emails/templates/lib/theme.ts +++ b/server/emails/templates/lib/theme.ts @@ -1,3 +1,5 @@ +import React from "react"; + export const themeColors = { theme: { extend: { diff --git a/server/lib/config.ts b/server/lib/config.ts index 720a5065..8e23ae0f 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -113,7 +113,9 @@ export class Config { private async checkKeyStatus() { const licenseStatus = await license.check(); - if (!licenseStatus.isHostLicensed) { + if ( + !licenseStatus.isHostLicensed + ) { this.checkSupporterKey(); } } diff --git a/server/lib/ip.ts b/server/lib/ip.ts index f30243cd..cf886fad 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -14,7 +14,7 @@ type IPVersion = 4 | 6; * Detects IP version from address string */ function detectIpVersion(ip: string): IPVersion { - return ip.includes(':') ? 6 : 4; + return ip.includes(":") ? 6 : 4; } /** @@ -24,34 +24,34 @@ function ipToBigInt(ip: string): bigint { const version = detectIpVersion(ip); if (version === 4) { - return ip.split('.') - .reduce((acc, octet) => { - const num = parseInt(octet); - if (isNaN(num) || num < 0 || num > 255) { - throw new Error(`Invalid IPv4 octet: ${octet}`); - } - return BigInt.asUintN(64, (acc << BigInt(8)) + BigInt(num)); - }, BigInt(0)); + return ip.split(".").reduce((acc, octet) => { + const num = parseInt(octet); + if (isNaN(num) || num < 0 || num > 255) { + throw new Error(`Invalid IPv4 octet: ${octet}`); + } + return BigInt.asUintN(64, (acc << BigInt(8)) + BigInt(num)); + }, BigInt(0)); } else { // Handle IPv6 // Expand :: notation let fullAddress = ip; - if (ip.includes('::')) { - const parts = ip.split('::'); - if (parts.length > 2) throw new Error('Invalid IPv6 address: multiple :: found'); - const missing = 8 - (parts[0].split(':').length + parts[1].split(':').length); - const padding = Array(missing).fill('0').join(':'); + if (ip.includes("::")) { + const parts = ip.split("::"); + if (parts.length > 2) + throw new Error("Invalid IPv6 address: multiple :: found"); + const missing = + 8 - (parts[0].split(":").length + parts[1].split(":").length); + const padding = Array(missing).fill("0").join(":"); fullAddress = `${parts[0]}:${padding}:${parts[1]}`; } - return fullAddress.split(':') - .reduce((acc, hextet) => { - const num = parseInt(hextet || '0', 16); - if (isNaN(num) || num < 0 || num > 65535) { - throw new Error(`Invalid IPv6 hextet: ${hextet}`); - } - return BigInt.asUintN(128, (acc << BigInt(16)) + BigInt(num)); - }, BigInt(0)); + return fullAddress.split(":").reduce((acc, hextet) => { + const num = parseInt(hextet || "0", 16); + if (isNaN(num) || num < 0 || num > 65535) { + throw new Error(`Invalid IPv6 hextet: ${hextet}`); + } + return BigInt.asUintN(128, (acc << BigInt(16)) + BigInt(num)); + }, BigInt(0)); } } @@ -65,11 +65,15 @@ function bigIntToIp(num: bigint, version: IPVersion): string { octets.unshift(Number(num & BigInt(255))); num = num >> BigInt(8); } - return octets.join('.'); + return octets.join("."); } else { const hextets: string[] = []; for (let i = 0; i < 8; i++) { - hextets.unshift(Number(num & BigInt(65535)).toString(16).padStart(4, '0')); + hextets.unshift( + Number(num & BigInt(65535)) + .toString(16) + .padStart(4, "0") + ); num = num >> BigInt(16); } // Compress zero sequences @@ -79,7 +83,7 @@ function bigIntToIp(num: bigint, version: IPVersion): string { let currentZeroLength = 0; for (let i = 0; i < hextets.length; i++) { - if (hextets[i] === '0000') { + if (hextets[i] === "0000") { if (currentZeroStart === -1) currentZeroStart = i; currentZeroLength++; if (currentZeroLength > maxZeroLength) { @@ -93,12 +97,14 @@ function bigIntToIp(num: bigint, version: IPVersion): string { } if (maxZeroLength > 1) { - hextets.splice(maxZeroStart, maxZeroLength, ''); - if (maxZeroStart === 0) hextets.unshift(''); - if (maxZeroStart + maxZeroLength === 8) hextets.push(''); + hextets.splice(maxZeroStart, maxZeroLength, ""); + if (maxZeroStart === 0) hextets.unshift(""); + if (maxZeroStart + maxZeroLength === 8) hextets.push(""); } - return hextets.map(h => h === '0000' ? '0' : h.replace(/^0+/, '')).join(':'); + return hextets + .map((h) => (h === "0000" ? "0" : h.replace(/^0+/, ""))) + .join(":"); } } @@ -106,7 +112,7 @@ function bigIntToIp(num: bigint, version: IPVersion): string { * Converts CIDR to IP range */ export function cidrToRange(cidr: string): IPRange { - const [ip, prefix] = cidr.split('/'); + const [ip, prefix] = cidr.split("/"); const version = detectIpVersion(ip); const prefixBits = parseInt(prefix); const ipBigInt = ipToBigInt(ip); @@ -118,7 +124,10 @@ export function cidrToRange(cidr: string): IPRange { } const shiftBits = BigInt(maxPrefix - prefixBits); - const mask = BigInt.asUintN(version === 4 ? 64 : 128, (BigInt(1) << shiftBits) - BigInt(1)); + const mask = BigInt.asUintN( + version === 4 ? 64 : 128, + (BigInt(1) << shiftBits) - BigInt(1) + ); const start = ipBigInt & ~mask; const end = start | mask; @@ -142,59 +151,66 @@ export function findNextAvailableCidr( } // If no existing CIDRs, use the IP version from startCidr - const version = startCidr - ? detectIpVersion(startCidr.split('/')[0]) - : 4; // Default to IPv4 if no startCidr provided + const version = startCidr ? detectIpVersion(startCidr.split("/")[0]) : 4; // Default to IPv4 if no startCidr provided // Use appropriate default startCidr if none provided startCidr = startCidr || (version === 4 ? "0.0.0.0/0" : "::/0"); // If there are existing CIDRs, ensure all are same version - if (existingCidrs.length > 0 && - existingCidrs.some(cidr => detectIpVersion(cidr.split('/')[0]) !== version)) { - throw new Error('All CIDRs must be of the same IP version'); + if ( + existingCidrs.length > 0 && + existingCidrs.some( + (cidr) => detectIpVersion(cidr.split("/")[0]) !== version + ) + ) { + throw new Error("All CIDRs must be of the same IP version"); } - + // Extract the network part from startCidr to ensure we stay in the right subnet const startCidrRange = cidrToRange(startCidr); - + // Convert existing CIDRs to ranges and sort them const existingRanges = existingCidrs - .map(cidr => cidrToRange(cidr)) + .map((cidr) => cidrToRange(cidr)) .sort((a, b) => (a.start < b.start ? -1 : 1)); - + // Calculate block size const maxPrefix = version === 4 ? 32 : 128; const blockSizeBigInt = BigInt(1) << BigInt(maxPrefix - blockSize); - + // Start from the beginning of the given CIDR let current = startCidrRange.start; const maxIp = startCidrRange.end; - + // Iterate through existing ranges for (let i = 0; i <= existingRanges.length; i++) { const nextRange = existingRanges[i]; - + // Align current to block size - const alignedCurrent = current + ((blockSizeBigInt - (current % blockSizeBigInt)) % blockSizeBigInt); - + const alignedCurrent = + current + + ((blockSizeBigInt - (current % blockSizeBigInt)) % blockSizeBigInt); + // Check if we've gone beyond the maximum allowed IP if (alignedCurrent + blockSizeBigInt - BigInt(1) > maxIp) { return null; } - + // If we're at the end of existing ranges or found a gap - if (!nextRange || alignedCurrent + blockSizeBigInt - BigInt(1) < nextRange.start) { + if ( + !nextRange || + alignedCurrent + blockSizeBigInt - BigInt(1) < nextRange.start + ) { return `${bigIntToIp(alignedCurrent, version)}/${blockSize}`; } - + // If next range overlaps with our search space, move past it if (nextRange.end >= startCidrRange.start && nextRange.start <= maxIp) { // Move current pointer to after the current range current = nextRange.end + BigInt(1); } } - + return null; } @@ -206,7 +222,7 @@ export function findNextAvailableCidr( */ export function isIpInCidr(ip: string, cidr: string): boolean { const ipVersion = detectIpVersion(ip); - const cidrVersion = detectIpVersion(cidr.split('/')[0]); + const cidrVersion = detectIpVersion(cidr.split("/")[0]); // If IP versions don't match, the IP cannot be in the CIDR range if (ipVersion !== cidrVersion) { @@ -219,11 +235,10 @@ export function isIpInCidr(ip: string, cidr: string): boolean { return ipBigInt >= range.start && ipBigInt <= range.end; } -export async function getNextAvailableClientSubnet(orgId: string): Promise { - const [org] = await db - .select() - .from(orgs) - .where(eq(orgs.orgId, orgId)); +export async function getNextAvailableClientSubnet( + orgId: string +): Promise { + const [org] = await db.select().from(orgs).where(eq(orgs.orgId, orgId)); const existingAddressesSites = await db .select({ @@ -240,15 +255,15 @@ export async function getNextAvailableClientSubnet(orgId: string): Promise `${site.address?.split("/")[0]}/32`), // we are overriding the 32 so that we pick individual addresses in the subnet of the org for the site and the client even though they are stored with the /block_size of the org - ...existingAddressesClients.map((client) => `${client.address.split("/")}/32`) + ...existingAddressesSites.map( + (site) => `${site.address?.split("/")[0]}/32` + ), // we are overriding the 32 so that we pick individual addresses in the subnet of the org for the site and the client even though they are stored with the /block_size of the org + ...existingAddressesClients.map( + (client) => `${client.address.split("/")}/32` + ) ].filter((address) => address !== null) as string[]; - let subnet = findNextAvailableCidr( - addresses, - 32, - org.subnet - ); // pick the sites address in the org + let subnet = findNextAvailableCidr(addresses, 32, org.subnet); // pick the sites address in the org if (!subnet) { throw new Error("No available subnets remaining in space"); } diff --git a/server/lib/readConfigFile.ts b/server/lib/readConfigFile.ts index f07e5628..f156478f 100644 --- a/server/lib/readConfigFile.ts +++ b/server/lib/readConfigFile.ts @@ -133,7 +133,10 @@ export const configSchema = z db: z.number().int().nonnegative().optional().default(0), tls: z .object({ - reject_unauthorized: z.boolean().optional().default(true) + reject_unauthorized: z + .boolean() + .optional() + .default(true) }) .optional() }) @@ -226,9 +229,9 @@ export const configSchema = z disable_local_sites: z.boolean().optional(), disable_basic_wireguard_sites: z.boolean().optional(), disable_config_managed_domains: z.boolean().optional(), - enable_clients: z.boolean().optional() + enable_clients: z.boolean().optional(), }) - .optional() + .optional(), }) .refine( (data) => { diff --git a/server/lib/validators.ts b/server/lib/validators.ts index 50ff5674..6c581e47 100644 --- a/server/lib/validators.ts +++ b/server/lib/validators.ts @@ -93,3 +93,1482 @@ export function isTargetValid(value: string | undefined) { return DOMAIN_REGEX.test(value); } + +export function isValidDomain(domain: string): boolean { + // Check overall length + if (domain.length > 253) return false; + + // Check for invalid characters or patterns + if ( + domain.startsWith(".") || + domain.endsWith(".") || + domain.includes("..") + ) { + return false; + } + + const labels = domain.split("."); + + // Must have at least 2 labels (domain + TLD) + if (labels.length < 2) return false; + + // Validate each label + for (const label of labels) { + if (label.length === 0 || label.length > 63) return false; + if (label.startsWith("-") || label.endsWith("-")) return false; + if (!/^[a-zA-Z0-9-]+$/.test(label)) return false; + } + + // TLD should be at least 2 characters and contain only letters + const tld = labels[labels.length - 1]; + if (tld.length < 2 || !/^[a-zA-Z]+$/.test(tld)) return false; + + // Check if TLD is in the list of valid TLDs + if (!validTlds.includes(tld.toUpperCase())) return false; + + return true; +} + +const validTlds = [ + "AAA", + "AARP", + "ABB", + "ABBOTT", + "ABBVIE", + "ABC", + "ABLE", + "ABOGADO", + "ABUDHABI", + "AC", + "ACADEMY", + "ACCENTURE", + "ACCOUNTANT", + "ACCOUNTANTS", + "ACO", + "ACTOR", + "AD", + "ADS", + "ADULT", + "AE", + "AEG", + "AERO", + "AETNA", + "AF", + "AFL", + "AFRICA", + "AG", + "AGAKHAN", + "AGENCY", + "AI", + "AIG", + "AIRBUS", + "AIRFORCE", + "AIRTEL", + "AKDN", + "AL", + "ALIBABA", + "ALIPAY", + "ALLFINANZ", + "ALLSTATE", + "ALLY", + "ALSACE", + "ALSTOM", + "AM", + "AMAZON", + "AMERICANEXPRESS", + "AMERICANFAMILY", + "AMEX", + "AMFAM", + "AMICA", + "AMSTERDAM", + "ANALYTICS", + "ANDROID", + "ANQUAN", + "ANZ", + "AO", + "AOL", + "APARTMENTS", + "APP", + "APPLE", + "AQ", + "AQUARELLE", + "AR", + "ARAB", + "ARAMCO", + "ARCHI", + "ARMY", + "ARPA", + "ART", + "ARTE", + "AS", + "ASDA", + "ASIA", + "ASSOCIATES", + "AT", + "ATHLETA", + "ATTORNEY", + "AU", + "AUCTION", + "AUDI", + "AUDIBLE", + "AUDIO", + "AUSPOST", + "AUTHOR", + "AUTO", + "AUTOS", + "AW", + "AWS", + "AX", + "AXA", + "AZ", + "AZURE", + "BA", + "BABY", + "BAIDU", + "BANAMEX", + "BAND", + "BANK", + "BAR", + "BARCELONA", + "BARCLAYCARD", + "BARCLAYS", + "BAREFOOT", + "BARGAINS", + "BASEBALL", + "BASKETBALL", + "BAUHAUS", + "BAYERN", + "BB", + "BBC", + "BBT", + "BBVA", + "BCG", + "BCN", + "BD", + "BE", + "BEATS", + "BEAUTY", + "BEER", + "BERLIN", + "BEST", + "BESTBUY", + "BET", + "BF", + "BG", + "BH", + "BHARTI", + "BI", + "BIBLE", + "BID", + "BIKE", + "BING", + "BINGO", + "BIO", + "BIZ", + "BJ", + "BLACK", + "BLACKFRIDAY", + "BLOCKBUSTER", + "BLOG", + "BLOOMBERG", + "BLUE", + "BM", + "BMS", + "BMW", + "BN", + "BNPPARIBAS", + "BO", + "BOATS", + "BOEHRINGER", + "BOFA", + "BOM", + "BOND", + "BOO", + "BOOK", + "BOOKING", + "BOSCH", + "BOSTIK", + "BOSTON", + "BOT", + "BOUTIQUE", + "BOX", + "BR", + "BRADESCO", + "BRIDGESTONE", + "BROADWAY", + "BROKER", + "BROTHER", + "BRUSSELS", + "BS", + "BT", + "BUILD", + "BUILDERS", + "BUSINESS", + "BUY", + "BUZZ", + "BV", + "BW", + "BY", + "BZ", + "BZH", + "CA", + "CAB", + "CAFE", + "CAL", + "CALL", + "CALVINKLEIN", + "CAM", + "CAMERA", + "CAMP", + "CANON", + "CAPETOWN", + "CAPITAL", + "CAPITALONE", + "CAR", + "CARAVAN", + "CARDS", + "CARE", + "CAREER", + "CAREERS", + "CARS", + "CASA", + "CASE", + "CASH", + "CASINO", + "CAT", + "CATERING", + "CATHOLIC", + "CBA", + "CBN", + "CBRE", + "CC", + "CD", + "CENTER", + "CEO", + "CERN", + "CF", + "CFA", + "CFD", + "CG", + "CH", + "CHANEL", + "CHANNEL", + "CHARITY", + "CHASE", + "CHAT", + "CHEAP", + "CHINTAI", + "CHRISTMAS", + "CHROME", + "CHURCH", + "CI", + "CIPRIANI", + "CIRCLE", + "CISCO", + "CITADEL", + "CITI", + "CITIC", + "CITY", + "CK", + "CL", + "CLAIMS", + "CLEANING", + "CLICK", + "CLINIC", + "CLINIQUE", + "CLOTHING", + "CLOUD", + "CLUB", + "CLUBMED", + "CM", + "CN", + "CO", + "COACH", + "CODES", + "COFFEE", + "COLLEGE", + "COLOGNE", + "COM", + "COMMBANK", + "COMMUNITY", + "COMPANY", + "COMPARE", + "COMPUTER", + "COMSEC", + "CONDOS", + "CONSTRUCTION", + "CONSULTING", + "CONTACT", + "CONTRACTORS", + "COOKING", + "COOL", + "COOP", + "CORSICA", + "COUNTRY", + "COUPON", + "COUPONS", + "COURSES", + "CPA", + "CR", + "CREDIT", + "CREDITCARD", + "CREDITUNION", + "CRICKET", + "CROWN", + "CRS", + "CRUISE", + "CRUISES", + "CU", + "CUISINELLA", + "CV", + "CW", + "CX", + "CY", + "CYMRU", + "CYOU", + "CZ", + "DAD", + "DANCE", + "DATA", + "DATE", + "DATING", + "DATSUN", + "DAY", + "DCLK", + "DDS", + "DE", + "DEAL", + "DEALER", + "DEALS", + "DEGREE", + "DELIVERY", + "DELL", + "DELOITTE", + "DELTA", + "DEMOCRAT", + "DENTAL", + "DENTIST", + "DESI", + "DESIGN", + "DEV", + "DHL", + "DIAMONDS", + "DIET", + "DIGITAL", + "DIRECT", + "DIRECTORY", + "DISCOUNT", + "DISCOVER", + "DISH", + "DIY", + "DJ", + "DK", + "DM", + "DNP", + "DO", + "DOCS", + "DOCTOR", + "DOG", + "DOMAINS", + "DOT", + "DOWNLOAD", + "DRIVE", + "DTV", + "DUBAI", + "DUNLOP", + "DUPONT", + "DURBAN", + "DVAG", + "DVR", + "DZ", + "EARTH", + "EAT", + "EC", + "ECO", + "EDEKA", + "EDU", + "EDUCATION", + "EE", + "EG", + "EMAIL", + "EMERCK", + "ENERGY", + "ENGINEER", + "ENGINEERING", + "ENTERPRISES", + "EPSON", + "EQUIPMENT", + "ER", + "ERICSSON", + "ERNI", + "ES", + "ESQ", + "ESTATE", + "ET", + "EU", + "EUROVISION", + "EUS", + "EVENTS", + "EXCHANGE", + "EXPERT", + "EXPOSED", + "EXPRESS", + "EXTRASPACE", + "FAGE", + "FAIL", + "FAIRWINDS", + "FAITH", + "FAMILY", + "FAN", + "FANS", + "FARM", + "FARMERS", + "FASHION", + "FAST", + "FEDEX", + "FEEDBACK", + "FERRARI", + "FERRERO", + "FI", + "FIDELITY", + "FIDO", + "FILM", + "FINAL", + "FINANCE", + "FINANCIAL", + "FIRE", + "FIRESTONE", + "FIRMDALE", + "FISH", + "FISHING", + "FIT", + "FITNESS", + "FJ", + "FK", + "FLICKR", + "FLIGHTS", + "FLIR", + "FLORIST", + "FLOWERS", + "FLY", + "FM", + "FO", + "FOO", + "FOOD", + "FOOTBALL", + "FORD", + "FOREX", + "FORSALE", + "FORUM", + "FOUNDATION", + "FOX", + "FR", + "FREE", + "FRESENIUS", + "FRL", + "FROGANS", + "FRONTIER", + "FTR", + "FUJITSU", + "FUN", + "FUND", + "FURNITURE", + "FUTBOL", + "FYI", + "GA", + "GAL", + "GALLERY", + "GALLO", + "GALLUP", + "GAME", + "GAMES", + "GAP", + "GARDEN", + "GAY", + "GB", + "GBIZ", + "GD", + "GDN", + "GE", + "GEA", + "GENT", + "GENTING", + "GEORGE", + "GF", + "GG", + "GGEE", + "GH", + "GI", + "GIFT", + "GIFTS", + "GIVES", + "GIVING", + "GL", + "GLASS", + "GLE", + "GLOBAL", + "GLOBO", + "GM", + "GMAIL", + "GMBH", + "GMO", + "GMX", + "GN", + "GODADDY", + "GOLD", + "GOLDPOINT", + "GOLF", + "GOO", + "GOODYEAR", + "GOOG", + "GOOGLE", + "GOP", + "GOT", + "GOV", + "GP", + "GQ", + "GR", + "GRAINGER", + "GRAPHICS", + "GRATIS", + "GREEN", + "GRIPE", + "GROCERY", + "GROUP", + "GS", + "GT", + "GU", + "GUCCI", + "GUGE", + "GUIDE", + "GUITARS", + "GURU", + "GW", + "GY", + "HAIR", + "HAMBURG", + "HANGOUT", + "HAUS", + "HBO", + "HDFC", + "HDFCBANK", + "HEALTH", + "HEALTHCARE", + "HELP", + "HELSINKI", + "HERE", + "HERMES", + "HIPHOP", + "HISAMITSU", + "HITACHI", + "HIV", + "HK", + "HKT", + "HM", + "HN", + "HOCKEY", + "HOLDINGS", + "HOLIDAY", + "HOMEDEPOT", + "HOMEGOODS", + "HOMES", + "HOMESENSE", + "HONDA", + "HORSE", + "HOSPITAL", + "HOST", + "HOSTING", + "HOT", + "HOTELS", + "HOTMAIL", + "HOUSE", + "HOW", + "HR", + "HSBC", + "HT", + "HU", + "HUGHES", + "HYATT", + "HYUNDAI", + "IBM", + "ICBC", + "ICE", + "ICU", + "ID", + "IE", + "IEEE", + "IFM", + "IKANO", + "IL", + "IM", + "IMAMAT", + "IMDB", + "IMMO", + "IMMOBILIEN", + "IN", + "INC", + "INDUSTRIES", + "INFINITI", + "INFO", + "ING", + "INK", + "INSTITUTE", + "INSURANCE", + "INSURE", + "INT", + "INTERNATIONAL", + "INTUIT", + "INVESTMENTS", + "IO", + "IPIRANGA", + "IQ", + "IR", + "IRISH", + "IS", + "ISMAILI", + "IST", + "ISTANBUL", + "IT", + "ITAU", + "ITV", + "JAGUAR", + "JAVA", + "JCB", + "JE", + "JEEP", + "JETZT", + "JEWELRY", + "JIO", + "JLL", + "JM", + "JMP", + "JNJ", + "JO", + "JOBS", + "JOBURG", + "JOT", + "JOY", + "JP", + "JPMORGAN", + "JPRS", + "JUEGOS", + "JUNIPER", + "KAUFEN", + "KDDI", + "KE", + "KERRYHOTELS", + "KERRYPROPERTIES", + "KFH", + "KG", + "KH", + "KI", + "KIA", + "KIDS", + "KIM", + "KINDLE", + "KITCHEN", + "KIWI", + "KM", + "KN", + "KOELN", + "KOMATSU", + "KOSHER", + "KP", + "KPMG", + "KPN", + "KR", + "KRD", + "KRED", + "KUOKGROUP", + "KW", + "KY", + "KYOTO", + "KZ", + "LA", + "LACAIXA", + "LAMBORGHINI", + "LAMER", + "LAND", + "LANDROVER", + "LANXESS", + "LASALLE", + "LAT", + "LATINO", + "LATROBE", + "LAW", + "LAWYER", + "LB", + "LC", + "LDS", + "LEASE", + "LECLERC", + "LEFRAK", + "LEGAL", + "LEGO", + "LEXUS", + "LGBT", + "LI", + "LIDL", + "LIFE", + "LIFEINSURANCE", + "LIFESTYLE", + "LIGHTING", + "LIKE", + "LILLY", + "LIMITED", + "LIMO", + "LINCOLN", + "LINK", + "LIVE", + "LIVING", + "LK", + "LLC", + "LLP", + "LOAN", + "LOANS", + "LOCKER", + "LOCUS", + "LOL", + "LONDON", + "LOTTE", + "LOTTO", + "LOVE", + "LPL", + "LPLFINANCIAL", + "LR", + "LS", + "LT", + "LTD", + "LTDA", + "LU", + "LUNDBECK", + "LUXE", + "LUXURY", + "LV", + "LY", + "MA", + "MADRID", + "MAIF", + "MAISON", + "MAKEUP", + "MAN", + "MANAGEMENT", + "MANGO", + "MAP", + "MARKET", + "MARKETING", + "MARKETS", + "MARRIOTT", + "MARSHALLS", + "MATTEL", + "MBA", + "MC", + "MCKINSEY", + "MD", + "ME", + "MED", + "MEDIA", + "MEET", + "MELBOURNE", + "MEME", + "MEMORIAL", + "MEN", + "MENU", + "MERCKMSD", + "MG", + "MH", + "MIAMI", + "MICROSOFT", + "MIL", + "MINI", + "MINT", + "MIT", + "MITSUBISHI", + "MK", + "ML", + "MLB", + "MLS", + "MM", + "MMA", + "MN", + "MO", + "MOBI", + "MOBILE", + "MODA", + "MOE", + "MOI", + "MOM", + "MONASH", + "MONEY", + "MONSTER", + "MORMON", + "MORTGAGE", + "MOSCOW", + "MOTO", + "MOTORCYCLES", + "MOV", + "MOVIE", + "MP", + "MQ", + "MR", + "MS", + "MSD", + "MT", + "MTN", + "MTR", + "MU", + "MUSEUM", + "MUSIC", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NAB", + "NAGOYA", + "NAME", + "NAVY", + "NBA", + "NC", + "NE", + "NEC", + "NET", + "NETBANK", + "NETFLIX", + "NETWORK", + "NEUSTAR", + "NEW", + "NEWS", + "NEXT", + "NEXTDIRECT", + "NEXUS", + "NF", + "NFL", + "NG", + "NGO", + "NHK", + "NI", + "NICO", + "NIKE", + "NIKON", + "NINJA", + "NISSAN", + "NISSAY", + "NL", + "NO", + "NOKIA", + "NORTON", + "NOW", + "NOWRUZ", + "NOWTV", + "NP", + "NR", + "NRA", + "NRW", + "NTT", + "NU", + "NYC", + "NZ", + "OBI", + "OBSERVER", + "OFFICE", + "OKINAWA", + "OLAYAN", + "OLAYANGROUP", + "OLLO", + "OM", + "OMEGA", + "ONE", + "ONG", + "ONL", + "ONLINE", + "OOO", + "OPEN", + "ORACLE", + "ORANGE", + "ORG", + "ORGANIC", + "ORIGINS", + "OSAKA", + "OTSUKA", + "OTT", + "OVH", + "PA", + "PAGE", + "PANASONIC", + "PARIS", + "PARS", + "PARTNERS", + "PARTS", + "PARTY", + "PAY", + "PCCW", + "PE", + "PET", + "PF", + "PFIZER", + "PG", + "PH", + "PHARMACY", + "PHD", + "PHILIPS", + "PHONE", + "PHOTO", + "PHOTOGRAPHY", + "PHOTOS", + "PHYSIO", + "PICS", + "PICTET", + "PICTURES", + "PID", + "PIN", + "PING", + "PINK", + "PIONEER", + "PIZZA", + "PK", + "PL", + "PLACE", + "PLAY", + "PLAYSTATION", + "PLUMBING", + "PLUS", + "PM", + "PN", + "PNC", + "POHL", + "POKER", + "POLITIE", + "PORN", + "POST", + "PR", + "PRAXI", + "PRESS", + "PRIME", + "PRO", + "PROD", + "PRODUCTIONS", + "PROF", + "PROGRESSIVE", + "PROMO", + "PROPERTIES", + "PROPERTY", + "PROTECTION", + "PRU", + "PRUDENTIAL", + "PS", + "PT", + "PUB", + "PW", + "PWC", + "PY", + "QA", + "QPON", + "QUEBEC", + "QUEST", + "RACING", + "RADIO", + "RE", + "READ", + "REALESTATE", + "REALTOR", + "REALTY", + "RECIPES", + "RED", + "REDSTONE", + "REDUMBRELLA", + "REHAB", + "REISE", + "REISEN", + "REIT", + "RELIANCE", + "REN", + "RENT", + "RENTALS", + "REPAIR", + "REPORT", + "REPUBLICAN", + "REST", + "RESTAURANT", + "REVIEW", + "REVIEWS", + "REXROTH", + "RICH", + "RICHARDLI", + "RICOH", + "RIL", + "RIO", + "RIP", + "RO", + "ROCKS", + "RODEO", + "ROGERS", + "ROOM", + "RS", + "RSVP", + "RU", + "RUGBY", + "RUHR", + "RUN", + "RW", + "RWE", + "RYUKYU", + "SA", + "SAARLAND", + "SAFE", + "SAFETY", + "SAKURA", + "SALE", + "SALON", + "SAMSCLUB", + "SAMSUNG", + "SANDVIK", + "SANDVIKCOROMANT", + "SANOFI", + "SAP", + "SARL", + "SAS", + "SAVE", + "SAXO", + "SB", + "SBI", + "SBS", + "SC", + "SCB", + "SCHAEFFLER", + "SCHMIDT", + "SCHOLARSHIPS", + "SCHOOL", + "SCHULE", + "SCHWARZ", + "SCIENCE", + "SCOT", + "SD", + "SE", + "SEARCH", + "SEAT", + "SECURE", + "SECURITY", + "SEEK", + "SELECT", + "SENER", + "SERVICES", + "SEVEN", + "SEW", + "SEX", + "SEXY", + "SFR", + "SG", + "SH", + "SHANGRILA", + "SHARP", + "SHELL", + "SHIA", + "SHIKSHA", + "SHOES", + "SHOP", + "SHOPPING", + "SHOUJI", + "SHOW", + "SI", + "SILK", + "SINA", + "SINGLES", + "SITE", + "SJ", + "SK", + "SKI", + "SKIN", + "SKY", + "SKYPE", + "SL", + "SLING", + "SM", + "SMART", + "SMILE", + "SN", + "SNCF", + "SO", + "SOCCER", + "SOCIAL", + "SOFTBANK", + "SOFTWARE", + "SOHU", + "SOLAR", + "SOLUTIONS", + "SONG", + "SONY", + "SOY", + "SPA", + "SPACE", + "SPORT", + "SPOT", + "SR", + "SRL", + "SS", + "ST", + "STADA", + "STAPLES", + "STAR", + "STATEBANK", + "STATEFARM", + "STC", + "STCGROUP", + "STOCKHOLM", + "STORAGE", + "STORE", + "STREAM", + "STUDIO", + "STUDY", + "STYLE", + "SU", + "SUCKS", + "SUPPLIES", + "SUPPLY", + "SUPPORT", + "SURF", + "SURGERY", + "SUZUKI", + "SV", + "SWATCH", + "SWISS", + "SX", + "SY", + "SYDNEY", + "SYSTEMS", + "SZ", + "TAB", + "TAIPEI", + "TALK", + "TAOBAO", + "TARGET", + "TATAMOTORS", + "TATAR", + "TATTOO", + "TAX", + "TAXI", + "TC", + "TCI", + "TD", + "TDK", + "TEAM", + "TECH", + "TECHNOLOGY", + "TEL", + "TEMASEK", + "TENNIS", + "TEVA", + "TF", + "TG", + "TH", + "THD", + "THEATER", + "THEATRE", + "TIAA", + "TICKETS", + "TIENDA", + "TIPS", + "TIRES", + "TIROL", + "TJ", + "TJMAXX", + "TJX", + "TK", + "TKMAXX", + "TL", + "TM", + "TMALL", + "TN", + "TO", + "TODAY", + "TOKYO", + "TOOLS", + "TOP", + "TORAY", + "TOSHIBA", + "TOTAL", + "TOURS", + "TOWN", + "TOYOTA", + "TOYS", + "TR", + "TRADE", + "TRADING", + "TRAINING", + "TRAVEL", + "TRAVELERS", + "TRAVELERSINSURANCE", + "TRUST", + "TRV", + "TT", + "TUBE", + "TUI", + "TUNES", + "TUSHU", + "TV", + "TVS", + "TW", + "TZ", + "UA", + "UBANK", + "UBS", + "UG", + "UK", + "UNICOM", + "UNIVERSITY", + "UNO", + "UOL", + "UPS", + "US", + "UY", + "UZ", + "VA", + "VACATIONS", + "VANA", + "VANGUARD", + "VC", + "VE", + "VEGAS", + "VENTURES", + "VERISIGN", + "VERSICHERUNG", + "VET", + "VG", + "VI", + "VIAJES", + "VIDEO", + "VIG", + "VIKING", + "VILLAS", + "VIN", + "VIP", + "VIRGIN", + "VISA", + "VISION", + "VIVA", + "VIVO", + "VLAANDEREN", + "VN", + "VODKA", + "VOLVO", + "VOTE", + "VOTING", + "VOTO", + "VOYAGE", + "VU", + "WALES", + "WALMART", + "WALTER", + "WANG", + "WANGGOU", + "WATCH", + "WATCHES", + "WEATHER", + "WEATHERCHANNEL", + "WEBCAM", + "WEBER", + "WEBSITE", + "WED", + "WEDDING", + "WEIBO", + "WEIR", + "WF", + "WHOSWHO", + "WIEN", + "WIKI", + "WILLIAMHILL", + "WIN", + "WINDOWS", + "WINE", + "WINNERS", + "WME", + "WOLTERSKLUWER", + "WOODSIDE", + "WORK", + "WORKS", + "WORLD", + "WOW", + "WS", + "WTC", + "WTF", + "XBOX", + "XEROX", + "XIHUAN", + "XIN", + "XN--11B4C3D", + "XN--1CK2E1B", + "XN--1QQW23A", + "XN--2SCRJ9C", + "XN--30RR7Y", + "XN--3BST00M", + "XN--3DS443G", + "XN--3E0B707E", + "XN--3HCRJ9C", + "XN--3PXU8K", + "XN--42C2D9A", + "XN--45BR5CYL", + "XN--45BRJ9C", + "XN--45Q11C", + "XN--4DBRK0CE", + "XN--4GBRIM", + "XN--54B7FTA0CC", + "XN--55QW42G", + "XN--55QX5D", + "XN--5SU34J936BGSG", + "XN--5TZM5G", + "XN--6FRZ82G", + "XN--6QQ986B3XL", + "XN--80ADXHKS", + "XN--80AO21A", + "XN--80AQECDR1A", + "XN--80ASEHDB", + "XN--80ASWG", + "XN--8Y0A063A", + "XN--90A3AC", + "XN--90AE", + "XN--90AIS", + "XN--9DBQ2A", + "XN--9ET52U", + "XN--9KRT00A", + "XN--B4W605FERD", + "XN--BCK1B9A5DRE4C", + "XN--C1AVG", + "XN--C2BR7G", + "XN--CCK2B3B", + "XN--CCKWCXETD", + "XN--CG4BKI", + "XN--CLCHC0EA0B2G2A9GCD", + "XN--CZR694B", + "XN--CZRS0T", + "XN--CZRU2D", + "XN--D1ACJ3B", + "XN--D1ALF", + "XN--E1A4C", + "XN--ECKVDTC9D", + "XN--EFVY88H", + "XN--FCT429K", + "XN--FHBEI", + "XN--FIQ228C5HS", + "XN--FIQ64B", + "XN--FIQS8S", + "XN--FIQZ9S", + "XN--FJQ720A", + "XN--FLW351E", + "XN--FPCRJ9C3D", + "XN--FZC2C9E2C", + "XN--FZYS8D69UVGM", + "XN--G2XX48C", + "XN--GCKR3F0F", + "XN--GECRJ9C", + "XN--GK3AT1E", + "XN--H2BREG3EVE", + "XN--H2BRJ9C", + "XN--H2BRJ9C8C", + "XN--HXT814E", + "XN--I1B6B1A6A2E", + "XN--IMR513N", + "XN--IO0A7I", + "XN--J1AEF", + "XN--J1AMH", + "XN--J6W193G", + "XN--JLQ480N2RG", + "XN--JVR189M", + "XN--KCRX77D1X4A", + "XN--KPRW13D", + "XN--KPRY57D", + "XN--KPUT3I", + "XN--L1ACC", + "XN--LGBBAT1AD8J", + "XN--MGB9AWBF", + "XN--MGBA3A3EJT", + "XN--MGBA3A4F16A", + "XN--MGBA7C0BBN0A", + "XN--MGBAAM7A8H", + "XN--MGBAB2BD", + "XN--MGBAH1A3HJKRD", + "XN--MGBAI9AZGQP6J", + "XN--MGBAYH7GPA", + "XN--MGBBH1A", + "XN--MGBBH1A71E", + "XN--MGBC0A9AZCG", + "XN--MGBCA7DZDO", + "XN--MGBCPQ6GPA1A", + "XN--MGBERP4A5D4AR", + "XN--MGBGU82A", + "XN--MGBI4ECEXP", + "XN--MGBPL2FH", + "XN--MGBT3DHD", + "XN--MGBTX2B", + "XN--MGBX4CD0AB", + "XN--MIX891F", + "XN--MK1BU44C", + "XN--MXTQ1M", + "XN--NGBC5AZD", + "XN--NGBE9E0A", + "XN--NGBRX", + "XN--NODE", + "XN--NQV7F", + "XN--NQV7FS00EMA", + "XN--NYQY26A", + "XN--O3CW4H", + "XN--OGBPF8FL", + "XN--OTU796D", + "XN--P1ACF", + "XN--P1AI", + "XN--PGBS0DH", + "XN--PSSY2U", + "XN--Q7CE6A", + "XN--Q9JYB4C", + "XN--QCKA1PMC", + "XN--QXA6A", + "XN--QXAM", + "XN--RHQV96G", + "XN--ROVU88B", + "XN--RVC1E0AM3E", + "XN--S9BRJ9C", + "XN--SES554G", + "XN--T60B56A", + "XN--TCKWE", + "XN--TIQ49XQYJ", + "XN--UNUP4Y", + "XN--VERMGENSBERATER-CTB", + "XN--VERMGENSBERATUNG-PWB", + "XN--VHQUV", + "XN--VUQ861B", + "XN--W4R85EL8FHU5DNRA", + "XN--W4RS40L", + "XN--WGBH1C", + "XN--WGBL6A", + "XN--XHQ521B", + "XN--XKC2AL3HYE2A", + "XN--XKC2DL3A5EE0H", + "XN--Y9A3AQ", + "XN--YFRO4I67O", + "XN--YGBI2AMMX", + "XN--ZFR164B", + "XXX", + "XYZ", + "YACHTS", + "YAHOO", + "YAMAXUN", + "YANDEX", + "YE", + "YODOBASHI", + "YOGA", + "YOKOHAMA", + "YOU", + "YOUTUBE", + "YT", + "YUN", + "ZA", + "ZAPPOS", + "ZARA", + "ZERO", + "ZIP", + "ZM", + "ZONE", + "ZUERICH", + "ZW", + "" +]; diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts index 8c8cae24..852012d4 100644 --- a/server/middlewares/index.ts +++ b/server/middlewares/index.ts @@ -14,10 +14,17 @@ export * from "./verifyAdmin"; export * from "./verifySetResourceUsers"; export * from "./verifyUserInRole"; export * from "./verifyAccessTokenAccess"; +export * from "./requestTimeout"; +export * from "./verifyClientAccess"; +export * from "./verifyUserHasAction"; export * from "./verifyUserIsServerAdmin"; export * from "./verifyIsLoggedInUser"; +export * from "./verifyIsLoggedInUser"; export * from "./verifyClientAccess"; export * from "./integration"; export * from "./verifyValidLicense"; export * from "./verifyUserHasAction"; export * from "./verifyApiKeyAccess"; +export * from "./verifyDomainAccess"; +export * from "./verifyClientsEnabled"; +export * from "./verifyUserIsOrgOwner"; diff --git a/server/middlewares/requestTimeout.ts b/server/middlewares/requestTimeout.ts new file mode 100644 index 00000000..8b5852b7 --- /dev/null +++ b/server/middlewares/requestTimeout.ts @@ -0,0 +1,35 @@ +import { Request, Response, NextFunction } from 'express'; +import logger from '@server/logger'; +import createHttpError from 'http-errors'; +import HttpCode from '@server/types/HttpCode'; + +export function requestTimeoutMiddleware(timeoutMs: number = 30000) { + return (req: Request, res: Response, next: NextFunction) => { + // Set a timeout for the request + const timeout = setTimeout(() => { + if (!res.headersSent) { + logger.error(`Request timeout: ${req.method} ${req.url} from ${req.ip}`); + return next( + createHttpError( + HttpCode.REQUEST_TIMEOUT, + 'Request timeout - operation took too long to complete' + ) + ); + } + }, timeoutMs); + + // Clear timeout when response finishes + res.on('finish', () => { + clearTimeout(timeout); + }); + + // Clear timeout when response closes + res.on('close', () => { + clearTimeout(timeout); + }); + + next(); + }; +} + +export default requestTimeoutMiddleware; diff --git a/server/middlewares/verifyClintsEnabled.ts b/server/middlewares/verifyClientsEnabled.ts similarity index 100% rename from server/middlewares/verifyClintsEnabled.ts rename to server/middlewares/verifyClientsEnabled.ts diff --git a/server/middlewares/verifyDomainAccess.ts b/server/middlewares/verifyDomainAccess.ts new file mode 100644 index 00000000..a6daf451 --- /dev/null +++ b/server/middlewares/verifyDomainAccess.ts @@ -0,0 +1,93 @@ +import { Request, Response, NextFunction } from "express"; +import { db, domains, orgDomains } from "@server/db"; +import { userOrgs, apiKeyOrg } from "@server/db"; +import { and, eq } from "drizzle-orm"; +import createHttpError from "http-errors"; +import HttpCode from "@server/types/HttpCode"; + +export async function verifyDomainAccess( + req: Request, + res: Response, + next: NextFunction +) { + try { + const userId = req.user!.userId; + const domainId = + req.params.domainId || req.body.apiKeyId || req.query.apiKeyId; + const orgId = req.params.orgId; + + if (!userId) { + return next( + createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated") + ); + } + + if (!orgId) { + return next( + createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID") + ); + } + + if (!domainId) { + return next( + createHttpError(HttpCode.BAD_REQUEST, "Invalid domain ID") + ); + } + + const [domain] = await db + .select() + .from(domains) + .innerJoin(orgDomains, eq(orgDomains.domainId, domains.domainId)) + .where( + and( + eq(orgDomains.domainId, domainId), + eq(orgDomains.orgId, orgId) + ) + ) + .limit(1); + + if (!domain.orgDomains) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Domain with ID ${domainId} not found` + ) + ); + } + + if (!req.userOrg) { + const userOrgRole = await db + .select() + .from(userOrgs) + .where( + and( + eq(userOrgs.userId, userId), + eq(userOrgs.orgId, apiKeyOrg.orgId) + ) + ) + .limit(1); + req.userOrg = userOrgRole[0]; + } + + if (!req.userOrg) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have access to this organization" + ) + ); + } + + const userOrgRoleId = req.userOrg.roleId; + req.userOrgRoleId = userOrgRoleId; + + return next(); + } catch (error) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Error verifying domain access" + ) + ); + } +} diff --git a/server/openApi.ts b/server/openApi.ts index 4df6cbdd..32cdb67b 100644 --- a/server/openApi.ts +++ b/server/openApi.ts @@ -14,5 +14,6 @@ export enum OpenAPITags { AccessToken = "Access Token", Idp = "Identity Provider", Client = "Client", - ApiKey = "API Key" + ApiKey = "API Key", + Domain = "Domain" } diff --git a/server/routers/auth/index.ts b/server/routers/auth/index.ts index 6955e16c..0a8f6339 100644 --- a/server/routers/auth/index.ts +++ b/server/routers/auth/index.ts @@ -11,4 +11,4 @@ export * from "./requestPasswordReset"; export * from "./resetPassword"; export * from "./checkResourceSession"; export * from "./setServerAdmin"; -export * from "./initialSetupComplete"; +export * from "./initialSetupComplete"; \ No newline at end of file diff --git a/server/routers/auth/requestTotpSecret.ts b/server/routers/auth/requestTotpSecret.ts index 2de35412..6dc2878b 100644 --- a/server/routers/auth/requestTotpSecret.ts +++ b/server/routers/auth/requestTotpSecret.ts @@ -12,6 +12,7 @@ import { createTOTPKeyURI } from "oslo/otp"; import logger from "@server/logger"; import { verifyPassword } from "@server/auth/password"; import { unauthorized } from "@server/auth/unauthorizedResponse"; +import config from "@server/lib/config"; import { UserType } from "@server/types/UserTypes"; export const requestTotpSecretBody = z @@ -73,7 +74,11 @@ export async function requestTotpSecret( const hex = crypto.getRandomValues(new Uint8Array(20)); const secret = encodeHex(hex); - const uri = createTOTPKeyURI("Pangolin", user.email!, hex); + const uri = createTOTPKeyURI( + "Pangolin", + user.email!, + hex + ); await db .update(users) diff --git a/server/routers/auth/signup.ts b/server/routers/auth/signup.ts index d905bd79..e3a21e45 100644 --- a/server/routers/auth/signup.ts +++ b/server/routers/auth/signup.ts @@ -57,8 +57,6 @@ export async function signup( const { email, password, inviteToken, inviteId } = parsedBody.data; - logger.debug("signup", { email, password, inviteToken, inviteId }); - const passwordHash = await hashPassword(password); const userId = generateId(15); diff --git a/server/routers/auth/verifyEmail.ts b/server/routers/auth/verifyEmail.ts index f707de22..97ab540b 100644 --- a/server/routers/auth/verifyEmail.ts +++ b/server/routers/auth/verifyEmail.ts @@ -4,7 +4,7 @@ import { z } from "zod"; import { fromError } from "zod-validation-error"; import HttpCode from "@server/types/HttpCode"; import { response } from "@server/lib"; -import { db } from "@server/db"; +import { db, userOrgs } from "@server/db"; import { User, emailVerificationCodes, users } from "@server/db"; import { eq } from "drizzle-orm"; import { isWithinExpirationDate } from "oslo"; diff --git a/server/routers/client/createClient.ts b/server/routers/client/createClient.ts index 5c529757..f5a67e68 100644 --- a/server/routers/client/createClient.ts +++ b/server/routers/client/createClient.ts @@ -94,7 +94,7 @@ export async function createClient( const { orgId } = parsedParams.data; - if (!req.userOrgRoleId) { + if (req.user && !req.userOrgRoleId) { return next( createHttpError(HttpCode.FORBIDDEN, "User does not have a role") ); @@ -208,7 +208,7 @@ export async function createClient( clientId: newClient.clientId }); - if (req.userOrgRoleId != adminRole[0].roleId) { + if (req.user && req.userOrgRoleId != adminRole[0].roleId) { // make sure the user can access the site trx.insert(userClients).values({ userId: req.user?.userId!, diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index 34099b96..ff03b2e0 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -126,7 +126,7 @@ export async function listClients( } const { orgId } = parsedParams.data; - if (orgId && orgId !== req.userOrgId) { + if (req.user && orgId && orgId !== req.userOrgId) { return next( createHttpError( HttpCode.FORBIDDEN, @@ -135,21 +135,29 @@ export async function listClients( ); } - const accessibleClients = await db - .select({ - clientId: sql`COALESCE(${userClients.clientId}, ${roleClients.clientId})` - }) - .from(userClients) - .fullJoin( - roleClients, - eq(userClients.clientId, roleClients.clientId) - ) - .where( - or( - eq(userClients.userId, req.user!.userId), - eq(roleClients.roleId, req.userOrgRoleId!) + let accessibleClients; + if (req.user) { + accessibleClients = await db + .select({ + clientId: sql`COALESCE(${userClients.clientId}, ${roleClients.clientId})` + }) + .from(userClients) + .fullJoin( + roleClients, + eq(userClients.clientId, roleClients.clientId) ) - ); + .where( + or( + eq(userClients.userId, req.user!.userId), + eq(roleClients.roleId, req.userOrgRoleId!) + ) + ); + } else { + accessibleClients = await db + .select({ clientId: clients.clientId }) + .from(clients) + .where(eq(clients.orgId, orgId)); + } const accessibleClientIds = accessibleClients.map( (client) => client.clientId diff --git a/server/routers/client/pickClientDefaults.ts b/server/routers/client/pickClientDefaults.ts index f4cec201..b1459400 100644 --- a/server/routers/client/pickClientDefaults.ts +++ b/server/routers/client/pickClientDefaults.ts @@ -7,6 +7,7 @@ import { generateId } from "@server/auth/sessions/app"; import { getNextAvailableClientSubnet } from "@server/lib/ip"; import { z } from "zod"; import { fromError } from "zod-validation-error"; +import { OpenAPITags, registry } from "@server/openApi"; export type PickClientDefaultsResponse = { olmId: string; @@ -20,6 +21,17 @@ const pickClientDefaultsSchema = z }) .strict(); +registry.registerPath({ + method: "get", + path: "/site/{siteId}/pick-client-defaults", + description: "Return pre-requisite data for creating a client.", + tags: [OpenAPITags.Client, OpenAPITags.Site], + request: { + params: pickClientDefaultsSchema + }, + responses: {} +}); + export async function pickClientDefaults( req: Request, res: Response, diff --git a/server/routers/domain/createOrgDomain.ts b/server/routers/domain/createOrgDomain.ts new file mode 100644 index 00000000..1b8720a1 --- /dev/null +++ b/server/routers/domain/createOrgDomain.ts @@ -0,0 +1,252 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db, Domain, domains, OrgDomains, orgDomains } from "@server/db"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { subdomainSchema } from "@server/lib/schemas"; +import { generateId } from "@server/auth/sessions/app"; +import { eq, and } from "drizzle-orm"; +import { isValidDomain } from "@server/lib/validators"; + +const paramsSchema = z + .object({ + orgId: z.string() + }) + .strict(); + +const bodySchema = z + .object({ + type: z.enum(["ns", "cname"]), + baseDomain: subdomainSchema + }) + .strict(); + +export type CreateDomainResponse = { + domainId: string; + nsRecords?: string[]; + cnameRecords?: { baseDomain: string; value: string }[]; + txtRecords?: { baseDomain: string; value: string }[]; +}; + +// Helper to check if a domain is a subdomain or equal to another domain +function isSubdomainOrEqual(a: string, b: string): boolean { + const aParts = a.toLowerCase().split("."); + const bParts = b.toLowerCase().split("."); + if (aParts.length < bParts.length) return false; + return aParts.slice(-bParts.length).join(".") === bParts.join("."); +} + +export async function createOrgDomain( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedBody = bodySchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const parsedParams = paramsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { orgId } = parsedParams.data; + const { type, baseDomain } = parsedBody.data; + + // Validate organization exists + if (!isValidDomain(baseDomain)) { + return next( + createHttpError(HttpCode.BAD_REQUEST, "Invalid domain format") + ); + } + + let numOrgDomains: OrgDomains[] | undefined; + let cnameRecords: CreateDomainResponse["cnameRecords"]; + let txtRecords: CreateDomainResponse["txtRecords"]; + let nsRecords: CreateDomainResponse["nsRecords"]; + let returned: Domain | undefined; + + await db.transaction(async (trx) => { + const [existing] = await trx + .select() + .from(domains) + .where( + and( + eq(domains.baseDomain, baseDomain), + eq(domains.type, type) + ) + ) + .leftJoin( + orgDomains, + eq(orgDomains.domainId, domains.domainId) + ); + + if (existing) { + const { + domains: existingDomain, + orgDomains: existingOrgDomain + } = existing; + + // user alrady added domain to this account + // always reject + if (existingOrgDomain?.orgId === orgId) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Domain is already added to this org" + ) + ); + } + + // domain already exists elsewhere + // check if it's already fully verified + if (existingDomain.verified) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Domain is already verified to an org" + ) + ); + } + } + + // --- Domain overlap logic --- + // Only consider existing verified domains + const verifiedDomains = await trx + .select() + .from(domains) + .where(eq(domains.verified, true)); + + if (type === "cname") { + // Block if a verified CNAME exists at the same name + const cnameExists = verifiedDomains.some( + (d) => d.type === "cname" && d.baseDomain === baseDomain + ); + if (cnameExists) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `A CNAME record already exists for ${baseDomain}. Only one CNAME record is allowed per domain.` + ) + ); + } + // Block if a verified NS exists at or below (same or subdomain) + const nsAtOrBelow = verifiedDomains.some( + (d) => + d.type === "ns" && + (isSubdomainOrEqual(baseDomain, d.baseDomain) || + baseDomain === d.baseDomain) + ); + if (nsAtOrBelow) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `A nameserver (NS) record exists at or below ${baseDomain}. You cannot create a CNAME record here.` + ) + ); + } + } else if (type === "ns") { + // Block if a verified NS exists at or below (same or subdomain) + const nsAtOrBelow = verifiedDomains.some( + (d) => + d.type === "ns" && + (isSubdomainOrEqual(baseDomain, d.baseDomain) || + baseDomain === d.baseDomain) + ); + if (nsAtOrBelow) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `A nameserver (NS) record already exists at or below ${baseDomain}. You cannot create another NS record here.` + ) + ); + } + } + + const domainId = generateId(15); + + const [insertedDomain] = await trx + .insert(domains) + .values({ + domainId, + baseDomain, + type + }) + .returning(); + + returned = insertedDomain; + + // add domain to account + await trx + .insert(orgDomains) + .values({ + orgId, + domainId + }) + .returning(); + + // TODO: This needs to be cross region and not hardcoded + if (type === "ns") { + nsRecords = ["ns-east.fossorial.io", "ns-west.fossorial.io"]; + } else if (type === "cname") { + cnameRecords = [ + { + value: `${domainId}.cname.fossorial.io`, + baseDomain: baseDomain + }, + { + value: `_acme-challenge.${domainId}.cname.fossorial.io`, + baseDomain: `_acme-challenge.${baseDomain}` + } + ]; + } + + numOrgDomains = await trx + .select() + .from(orgDomains) + .where(eq(orgDomains.orgId, orgId)); + }); + + if (!returned) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to create domain" + ) + ); + } + + return response(res, { + data: { + domainId: returned.domainId, + cnameRecords, + txtRecords, + nsRecords + }, + success: true, + error: false, + message: "Domain created successfully", + status: HttpCode.CREATED + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/domain/deleteOrgDomain.ts b/server/routers/domain/deleteOrgDomain.ts new file mode 100644 index 00000000..aea2a2fa --- /dev/null +++ b/server/routers/domain/deleteOrgDomain.ts @@ -0,0 +1,72 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db, domains, OrgDomains, orgDomains } from "@server/db"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { and, eq } from "drizzle-orm"; + +const paramsSchema = z + .object({ + domainId: z.string(), + orgId: z.string() + }) + .strict(); + +export type DeleteAccountDomainResponse = { + success: boolean; +}; + +export async function deleteAccountDomain( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsed = paramsSchema.safeParse(req.params); + if (!parsed.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsed.error).toString() + ) + ); + } + const { domainId, orgId } = parsed.data; + + let numOrgDomains: OrgDomains[] | undefined; + + await db.transaction(async (trx) => { + await trx + .delete(orgDomains) + .where( + and( + eq(orgDomains.orgId, orgId), + eq(orgDomains.domainId, domainId) + ) + ); + + await trx.delete(domains).where(eq(domains.domainId, domainId)); + + numOrgDomains = await trx + .select() + .from(orgDomains) + .where(eq(orgDomains.orgId, orgId)); + }); + + return response(res, { + data: { success: true }, + success: true, + error: false, + message: "Domain deleted from account successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/domain/index.ts b/server/routers/domain/index.ts index 2233b069..c0cafafe 100644 --- a/server/routers/domain/index.ts +++ b/server/routers/domain/index.ts @@ -1 +1,4 @@ export * from "./listDomains"; +export * from "./createOrgDomain"; +export * from "./deleteOrgDomain"; +export * from "./restartOrgDomain"; \ No newline at end of file diff --git a/server/routers/domain/listDomains.ts b/server/routers/domain/listDomains.ts index a8216c5f..cb968854 100644 --- a/server/routers/domain/listDomains.ts +++ b/server/routers/domain/listDomains.ts @@ -37,7 +37,11 @@ async function queryDomains(orgId: string, limit: number, offset: number) { const res = await db .select({ domainId: domains.domainId, - baseDomain: domains.baseDomain + baseDomain: domains.baseDomain, + verified: domains.verified, + type: domains.type, + failed: domains.failed, + tries: domains.tries, }) .from(orgDomains) .where(eq(orgDomains.orgId, orgId)) @@ -112,7 +116,7 @@ export async function listDomains( }, success: true, error: false, - message: "Users retrieved successfully", + message: "Domains retrieved successfully", status: HttpCode.OK }); } catch (error) { diff --git a/server/routers/domain/restartOrgDomain.ts b/server/routers/domain/restartOrgDomain.ts new file mode 100644 index 00000000..f40f2516 --- /dev/null +++ b/server/routers/domain/restartOrgDomain.ts @@ -0,0 +1,57 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db, domains } from "@server/db"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { and, eq } from "drizzle-orm"; + +const paramsSchema = z + .object({ + domainId: z.string(), + orgId: z.string() + }) + .strict(); + +export type RestartOrgDomainResponse = { + success: boolean; +}; + +export async function restartOrgDomain( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsed = paramsSchema.safeParse(req.params); + if (!parsed.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsed.error).toString() + ) + ); + } + const { domainId, orgId } = parsed.data; + + await db + .update(domains) + .set({ failed: false, tries: 0 }) + .where(and(eq(domains.domainId, domainId))); + + return response(res, { + data: { success: true }, + success: true, + error: false, + message: "Domain restarted successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/external.ts b/server/routers/external.ts index 1b57e380..69921804 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -33,15 +33,16 @@ import { verifyClientAccess, verifyApiKeyAccess, createStore, + verifyDomainAccess, + verifyClientsEnabled, + verifyUserHasAction, + verifyUserIsOrgOwner } from "@server/middlewares"; -import { verifyUserHasAction } from "../middlewares/verifyUserHasAction"; import { ActionsEnum } from "@server/auth/actions"; -import { verifyUserIsOrgOwner } from "../middlewares/verifyUserIsOrgOwner"; import { createNewt, getNewtToken } from "./newt"; import { getOlmToken } from "./olm"; import rateLimit from "express-rate-limit"; import createHttpError from "http-errors"; -import { verifyClientsEnabled } from "@server/middlewares/verifyClintsEnabled"; // Root routes export const unauthenticated = Router(); @@ -54,10 +55,7 @@ unauthenticated.get("/", (_, res) => { export const authenticated = Router(); authenticated.use(verifySessionUserMiddleware); -authenticated.get( - "/pick-org-defaults", - org.pickOrgDefaults -); +authenticated.get("/pick-org-defaults", org.pickOrgDefaults); authenticated.get("/org/checkId", org.checkId); authenticated.put("/org", getUserOrgs, org.createOrg); @@ -750,6 +748,29 @@ authenticated.get( apiKeys.getApiKey ); +authenticated.put( + `/org/:orgId/domain`, + verifyOrgAccess, + verifyUserHasAction(ActionsEnum.createOrgDomain), + domain.createOrgDomain +); + +authenticated.post( + `/org/:orgId/domain/:domainId/restart`, + verifyOrgAccess, + verifyDomainAccess, + verifyUserHasAction(ActionsEnum.restartOrgDomain), + domain.restartOrgDomain +); + +authenticated.delete( + `/org/:orgId/domain/:domainId`, + verifyOrgAccess, + verifyDomainAccess, + verifyUserHasAction(ActionsEnum.deleteOrgDomain), + domain.deleteAccountDomain +); + // Auth routes export const authRouter = Router(); unauthenticated.use("/auth", authRouter); diff --git a/server/routers/gerbil/receiveBandwidth.ts b/server/routers/gerbil/receiveBandwidth.ts index b6205914..5e672d0f 100644 --- a/server/routers/gerbil/receiveBandwidth.ts +++ b/server/routers/gerbil/receiveBandwidth.ts @@ -1,5 +1,5 @@ import { Request, Response, NextFunction } from "express"; -import { eq, and, lt, inArray } from "drizzle-orm"; +import { eq, and, lt, inArray, sql } from "drizzle-orm"; import { sites } from "@server/db"; import { db } from "@server/db"; import logger from "@server/logger"; @@ -7,6 +7,9 @@ import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; +// Track sites that are already offline to avoid unnecessary queries +const offlineSites = new Set(); + interface PeerBandwidth { publicKey: string; bytesIn: number; @@ -28,43 +31,62 @@ export const receiveBandwidth = async ( const currentTime = new Date(); const oneMinuteAgo = new Date(currentTime.getTime() - 60000); // 1 minute ago + logger.debug(`Received data: ${JSON.stringify(bandwidthData)}`); + await db.transaction(async (trx) => { // First, handle sites that are actively reporting bandwidth - const activePeers = bandwidthData.filter(peer => peer.bytesIn > 0 || peer.bytesOut > 0); - + const activePeers = bandwidthData.filter(peer => peer.bytesIn > 0); // Bytesout will have data as it tries to send keep alive messages + if (activePeers.length > 0) { - // Get all active sites in one query - const activeSites = await trx - .select() - .from(sites) - .where(inArray(sites.pubKey, activePeers.map(p => p.publicKey))); + // Remove any active peers from offline tracking since they're sending data + activePeers.forEach(peer => offlineSites.delete(peer.publicKey)); - // Create a map for quick lookup - const siteMap = new Map(); - activeSites.forEach(site => { - siteMap.set(site.pubKey, site); - }); + // Aggregate usage data by organization + const orgUsageMap = new Map(); + const orgUptimeMap = new Map(); - // Update sites with actual bandwidth usage + // Update all active sites with bandwidth data and get the site data in one operation + const updatedSites = []; for (const peer of activePeers) { - const site = siteMap.get(peer.publicKey); - if (!site) continue; - - await trx + const updatedSite = await trx .update(sites) .set({ - megabytesOut: (site.megabytesOut || 0) + peer.bytesIn, - megabytesIn: (site.megabytesIn || 0) + peer.bytesOut, + megabytesOut: sql`${sites.megabytesOut} + ${peer.bytesIn}`, + megabytesIn: sql`${sites.megabytesIn} + ${peer.bytesOut}`, lastBandwidthUpdate: currentTime.toISOString(), online: true }) - .where(eq(sites.siteId, site.siteId)); + .where(eq(sites.pubKey, peer.publicKey)) + .returning({ + online: sites.online, + orgId: sites.orgId, + siteId: sites.siteId, + lastBandwidthUpdate: sites.lastBandwidthUpdate, + }); + + if (updatedSite.length > 0) { + updatedSites.push({ ...updatedSite[0], peer }); + } + } + + // Calculate org usage aggregations using the updated site data + for (const { peer, ...site } of updatedSites) { + // Aggregate bandwidth usage for the org + const totalBandwidth = peer.bytesIn + peer.bytesOut; + const currentOrgUsage = orgUsageMap.get(site.orgId) || 0; + orgUsageMap.set(site.orgId, currentOrgUsage + totalBandwidth); + + // Add 10 seconds of uptime for each active site + const currentOrgUptime = orgUptimeMap.get(site.orgId) || 0; + orgUptimeMap.set(site.orgId, currentOrgUptime + 10 / 60); // Store in minutes and jut add 10 seconds } } // Handle sites that reported zero bandwidth but need online status updated - const zeroBandwidthPeers = bandwidthData.filter(peer => peer.bytesIn === 0 && peer.bytesOut === 0); - + const zeroBandwidthPeers = bandwidthData.filter(peer => + peer.bytesIn === 0 && !offlineSites.has(peer.publicKey) // Bytesout will have data as it tries to send keep alive messages + ); + if (zeroBandwidthPeers.length > 0) { const zeroBandwidthSites = await trx .select() @@ -91,18 +113,14 @@ export const receiveBandwidth = async ( await trx .update(sites) .set({ - lastBandwidthUpdate: currentTime.toISOString(), online: newOnlineStatus }) .where(eq(sites.siteId, site.siteId)); - } else { - // Just update the heartbeat timestamp - await trx - .update(sites) - .set({ - lastBandwidthUpdate: currentTime.toISOString() - }) - .where(eq(sites.siteId, site.siteId)); + + // If site went offline, add it to our tracking set + if (!newOnlineStatus && site.pubKey) { + offlineSites.add(site.pubKey); + } } } } diff --git a/server/routers/idp/validateOidcCallback.ts b/server/routers/idp/validateOidcCallback.ts index 3d28db24..6003c63d 100644 --- a/server/routers/idp/validateOidcCallback.ts +++ b/server/routers/idp/validateOidcCallback.ts @@ -11,6 +11,7 @@ import { idpOidcConfig, idpOrg, orgs, + Role, roles, userOrgs, users @@ -307,6 +308,8 @@ export async function validateOidcCallback( let existingUserId = existingUser?.userId; + let orgUserCounts: { orgId: string; userCount: number }[] = []; + // sync the user with the orgs and roles await db.transaction(async (trx) => { let userId = existingUser?.userId; @@ -410,6 +413,19 @@ export async function validateOidcCallback( })) ); } + + // Loop through all the orgs and get the total number of users from the userOrgs table + for (const org of currentUserOrgs) { + const userCount = await trx + .select() + .from(userOrgs) + .where(eq(userOrgs.orgId, org.orgId)); + + orgUserCounts.push({ + orgId: org.orgId, + userCount: userCount.length + }); + } }); const token = generateSessionToken(); diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index cdf69c66..5950aa93 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -87,7 +87,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { let siteSubnet = oldSite.subnet; let exitNodeIdToQuery = oldSite.exitNodeId; - if (exitNodeId && oldSite.exitNodeId !== exitNodeId) { + if (exitNodeId && (oldSite.exitNodeId !== exitNodeId || !oldSite.subnet)) { // This effectively moves the exit node to the new one exitNodeIdToQuery = exitNodeId; // Use the provided exitNodeId if it differs from the site's exitNodeId @@ -105,7 +105,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { .limit(1); const blockSize = config.getRawConfig().gerbil.site_block_size; - const subnets = sitesQuery.map((site) => site.subnet); + const subnets = sitesQuery.map((site) => site.subnet).filter((subnet) => subnet !== null); subnets.push(exitNode.address.replace(/\/\d+$/, `/${blockSize}`)); const newSubnet = findNextAvailableCidr( subnets, diff --git a/server/routers/olm/createOlm.ts b/server/routers/olm/createOlm.ts index 03864703..3066e4ea 100644 --- a/server/routers/olm/createOlm.ts +++ b/server/routers/olm/createOlm.ts @@ -49,7 +49,7 @@ export async function createNewt( const { newtId, secret } = parsedBody.data; - if (!req.userOrgRoleId) { + if (req.user && !req.userOrgRoleId) { return next( createHttpError(HttpCode.FORBIDDEN, "User does not have a role") ); diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index fe73a842..245e0928 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -33,8 +33,6 @@ const createOrgSchema = z }) .strict(); -// const MAX_ORGS = 5; - registry.registerPath({ method: "put", path: "/org", @@ -80,16 +78,6 @@ export async function createOrg( ); } - // const userOrgIds = req.userOrgIds; - // if (userOrgIds && userOrgIds.length > MAX_ORGS) { - // return next( - // createHttpError( - // HttpCode.FORBIDDEN, - // `Maximum number of organizations reached.` - // ) - // ); - // } - const { orgId, name, subnet } = parsedBody.data; if (!isValidCIDR(subnet)) { @@ -147,7 +135,7 @@ export async function createOrg( .values({ orgId, name, - subnet, + subnet }) .returning(); @@ -182,15 +170,13 @@ export async function createOrg( const actionIds = await trx.select().from(actions).execute(); if (actionIds.length > 0) { - await trx - .insert(roleActions) - .values( - actionIds.map((action) => ({ - roleId, - actionId: action.actionId, - orgId: newOrg[0].orgId - })) - ); + await trx.insert(roleActions).values( + actionIds.map((action) => ({ + roleId, + actionId: action.actionId, + orgId: newOrg[0].orgId + })) + ); } if (allDomains.length) { @@ -227,7 +213,7 @@ export async function createOrg( orgId: newOrg[0].orgId, roleId: roleId, isOwner: true - }); + }); } const memberRole = await trx diff --git a/server/routers/org/deleteOrg.ts b/server/routers/org/deleteOrg.ts index 5b2accce..41b491a2 100644 --- a/server/routers/org/deleteOrg.ts +++ b/server/routers/org/deleteOrg.ts @@ -89,6 +89,8 @@ export async function deleteOrg( .where(eq(sites.orgId, orgId)) .limit(1); + const deletedNewtIds: string[] = []; + await db.transaction(async (trx) => { if (sites) { for (const site of orgSites) { @@ -102,11 +104,7 @@ export async function deleteOrg( .where(eq(newts.siteId, site.siteId)) .returning(); if (deletedNewt) { - const payload = { - type: `newt/terminate`, - data: {} - }; - sendToClient(deletedNewt.newtId, payload); + deletedNewtIds.push(deletedNewt.newtId); // delete all of the sessions for the newt await trx @@ -131,6 +129,18 @@ export async function deleteOrg( await trx.delete(orgs).where(eq(orgs.orgId, orgId)); }); + // Send termination messages outside of transaction to prevent blocking + for (const newtId of deletedNewtIds) { + const payload = { + type: `newt/terminate`, + data: {} + }; + // Don't await this to prevent blocking the response + sendToClient(newtId, payload).catch(error => { + logger.error("Failed to send termination message to newt:", error); + }); + } + return response(res, { data: null, success: true, diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index 6dc852e4..3fb2a733 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -69,7 +69,8 @@ function queryResources( http: resources.http, protocol: resources.protocol, proxyPort: resources.proxyPort, - enabled: resources.enabled + enabled: resources.enabled, + domainId: resources.domainId }) .from(resources) .leftJoin(sites, eq(resources.siteId, sites.siteId)) @@ -103,7 +104,8 @@ function queryResources( http: resources.http, protocol: resources.protocol, proxyPort: resources.proxyPort, - enabled: resources.enabled + enabled: resources.enabled, + domainId: resources.domainId }) .from(resources) .leftJoin(sites, eq(resources.siteId, sites.siteId)) diff --git a/server/routers/site/createSite.ts b/server/routers/site/createSite.ts index 5132196d..7e86b524 100644 --- a/server/routers/site/createSite.ts +++ b/server/routers/site/createSite.ts @@ -38,7 +38,7 @@ const createSiteSchema = z subnet: z.string().optional(), newtId: z.string().optional(), secret: z.string().optional(), - address: z.string().optional(), + // address: z.string().optional(), type: z.enum(["newt", "wireguard", "local"]) }) .strict() @@ -97,7 +97,7 @@ export async function createSite( subnet, newtId, secret, - address + // address } = parsedBody.data; const parsedParams = createSiteParamsSchema.safeParse(req.params); @@ -129,58 +129,58 @@ export async function createSite( ); } - let updatedAddress = null; - if (address) { - if (!isValidIP(address)) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Invalid subnet format. Please provide a valid CIDR notation." - ) - ); - } - - if (!isIpInCidr(address, org.subnet)) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "IP is not in the CIDR range of the subnet." - ) - ); - } - - updatedAddress = `${address}/${org.subnet.split("/")[1]}`; // we want the block size of the whole org - - // make sure the subnet is unique - const addressExistsSites = await db - .select() - .from(sites) - .where(eq(sites.address, updatedAddress)) - .limit(1); - - if (addressExistsSites.length > 0) { - return next( - createHttpError( - HttpCode.CONFLICT, - `Subnet ${subnet} already exists` - ) - ); - } - - const addressExistsClients = await db - .select() - .from(sites) - .where(eq(sites.subnet, updatedAddress)) - .limit(1); - if (addressExistsClients.length > 0) { - return next( - createHttpError( - HttpCode.CONFLICT, - `Subnet ${subnet} already exists` - ) - ); - } - } + // let updatedAddress = null; + // if (address) { + // if (!isValidIP(address)) { + // return next( + // createHttpError( + // HttpCode.BAD_REQUEST, + // "Invalid subnet format. Please provide a valid CIDR notation." + // ) + // ); + // } + // + // if (!isIpInCidr(address, org.subnet)) { + // return next( + // createHttpError( + // HttpCode.BAD_REQUEST, + // "IP is not in the CIDR range of the subnet." + // ) + // ); + // } + // + // updatedAddress = `${address}/${org.subnet.split("/")[1]}`; // we want the block size of the whole org + // + // // make sure the subnet is unique + // const addressExistsSites = await db + // .select() + // .from(sites) + // .where(eq(sites.address, updatedAddress)) + // .limit(1); + // + // if (addressExistsSites.length > 0) { + // return next( + // createHttpError( + // HttpCode.CONFLICT, + // `Subnet ${subnet} already exists` + // ) + // ); + // } + // + // const addressExistsClients = await db + // .select() + // .from(sites) + // .where(eq(sites.subnet, updatedAddress)) + // .limit(1); + // if (addressExistsClients.length > 0) { + // return next( + // createHttpError( + // HttpCode.CONFLICT, + // `Subnet ${subnet} already exists` + // ) + // ); + // } + // } const niceId = await getUniqueSiteName(orgId); @@ -205,7 +205,7 @@ export async function createSite( exitNodeId, name, niceId, - address: updatedAddress || null, + // address: updatedAddress || null, subnet, type, dockerSocketEnabled: type == "newt", @@ -221,7 +221,7 @@ export async function createSite( orgId, name, niceId, - address: updatedAddress || null, + // address: updatedAddress || null, type, dockerSocketEnabled: type == "newt", subnet: "0.0.0.0/0" diff --git a/server/routers/site/deleteSite.ts b/server/routers/site/deleteSite.ts index 1554ad2b..4af2feae 100644 --- a/server/routers/site/deleteSite.ts +++ b/server/routers/site/deleteSite.ts @@ -62,6 +62,8 @@ export async function deleteSite( ); } + let deletedNewtId: string | null = null; + await db.transaction(async (trx) => { if (site.pubKey) { if (site.type == "wireguard") { @@ -73,11 +75,7 @@ export async function deleteSite( .where(eq(newts.siteId, siteId)) .returning(); if (deletedNewt) { - const payload = { - type: `newt/terminate`, - data: {} - }; - sendToClient(deletedNewt.newtId, payload); + deletedNewtId = deletedNewt.newtId; // delete all of the sessions for the newt await trx @@ -90,6 +88,18 @@ export async function deleteSite( await trx.delete(sites).where(eq(sites.siteId, siteId)); }); + // Send termination message outside of transaction to prevent blocking + if (deletedNewtId) { + const payload = { + type: `newt/terminate`, + data: {} + }; + // Don't await this to prevent blocking the response + sendToClient(deletedNewtId, payload).catch(error => { + logger.error("Failed to send termination message to newt:", error); + }); + } + return response(res, { data: null, success: true, diff --git a/server/routers/site/pickSiteDefaults.ts b/server/routers/site/pickSiteDefaults.ts index 68107d95..430fb95a 100644 --- a/server/routers/site/pickSiteDefaults.ts +++ b/server/routers/site/pickSiteDefaults.ts @@ -20,10 +20,10 @@ export type PickSiteDefaultsResponse = { name: string; listenPort: number; endpoint: string; - subnet: string; + subnet: string; // TODO: make optional? newtId: string; newtSecret: string; - clientAddress: string; + clientAddress?: string; }; registry.registerPath({ @@ -86,7 +86,7 @@ export async function pickSiteDefaults( .where(eq(sites.exitNodeId, exitNode.exitNodeId)); // TODO: we need to lock this subnet for some time so someone else does not take it - let subnets = sitesQuery.map((site) => site.subnet); + let subnets = sitesQuery.map((site) => site.subnet).filter((subnet) => subnet !== null); // exclude the exit node address by replacing after the / with a site block size subnets.push( exitNode.address.replace( @@ -108,17 +108,17 @@ export async function pickSiteDefaults( ); } - const newClientAddress = await getNextAvailableClientSubnet(orgId); - if (!newClientAddress) { - return next( - createHttpError( - HttpCode.INTERNAL_SERVER_ERROR, - "No available subnet found" - ) - ); - } + // const newClientAddress = await getNextAvailableClientSubnet(orgId); + // if (!newClientAddress) { + // return next( + // createHttpError( + // HttpCode.INTERNAL_SERVER_ERROR, + // "No available subnet found" + // ) + // ); + // } - const clientAddress = newClientAddress.split("/")[0]; + // const clientAddress = newClientAddress.split("/")[0]; const newtId = generateId(15); const secret = generateId(48); @@ -133,7 +133,7 @@ export async function pickSiteDefaults( endpoint: exitNode.endpoint, // subnet: `${newSubnet.split("/")[0]}/${config.getRawConfig().gerbil.block_size}`, // we want the block size of the whole subnet subnet: newSubnet, - clientAddress: clientAddress, + // clientAddress: clientAddress, newtId, newtSecret: secret }, diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index 15201e81..95968858 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -56,12 +56,8 @@ export async function traefikConfigProvider( .select({ // Resource fields resourceId: resources.resourceId, - subdomain: resources.subdomain, fullDomain: resources.fullDomain, ssl: resources.ssl, - blockAccess: resources.blockAccess, - sso: resources.sso, - emailWhitelistEnabled: resources.emailWhitelistEnabled, http: resources.http, proxyPort: resources.proxyPort, protocol: resources.protocol, @@ -74,10 +70,6 @@ export async function traefikConfigProvider( subnet: sites.subnet, exitNodeId: sites.exitNodeId, }, - // Org fields - org: { - orgId: orgs.orgId - }, enabled: resources.enabled, stickySession: resources.stickySession, tlsServerName: resources.tlsServerName, @@ -85,7 +77,6 @@ export async function traefikConfigProvider( }) .from(resources) .innerJoin(sites, eq(sites.siteId, resources.siteId)) - .innerJoin(orgs, eq(resources.orgId, orgs.orgId)) .where(eq(sites.exitNodeId, currentExitNodeId)); // Get all resource IDs from the first query @@ -179,7 +170,6 @@ export async function traefikConfigProvider( for (const resource of allResources) { const targets = resource.targets as Target[]; const site = resource.site; - const org = resource.org; const routerName = `${resource.resourceId}-router`; const serviceName = `${resource.resourceId}-service`; @@ -203,11 +193,6 @@ export async function traefikConfigProvider( continue; } - // HTTP configuration remains the same - if (!resource.subdomain && !resource.isBaseDomain) { - continue; - } - // add routers and services empty objects if they don't exist if (!config_output.http.routers) { config_output.http.routers = {}; @@ -299,7 +284,7 @@ export async function traefikConfigProvider( } else if (site.type === "newt") { if ( !target.internalPort || - !target.method + !target.method || !site.subnet ) { return false; } @@ -315,7 +300,7 @@ export async function traefikConfigProvider( url: `${target.method}://${target.ip}:${target.port}` }; } else if (site.type === "newt") { - const ip = site.subnet.split("/")[0]; + const ip = site.subnet!.split("/")[0]; return { url: `${target.method}://${ip}:${target.internalPort}` }; @@ -409,7 +394,7 @@ export async function traefikConfigProvider( return false; } } else if (site.type === "newt") { - if (!target.internalPort) { + if (!target.internalPort || !site.subnet) { return false; } } @@ -424,7 +409,7 @@ export async function traefikConfigProvider( address: `${target.ip}:${target.port}` }; } else if (site.type === "newt") { - const ip = site.subnet.split("/")[0]; + const ip = site.subnet!.split("/")[0]; return { address: `${ip}:${target.internalPort}` }; diff --git a/server/routers/user/acceptInvite.ts b/server/routers/user/acceptInvite.ts index 115168b9..73bed018 100644 --- a/server/routers/user/acceptInvite.ts +++ b/server/routers/user/acceptInvite.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db } from "@server/db"; +import { db, UserOrg } from "@server/db"; import { roles, userInvites, userOrgs, users } from "@server/db"; import { eq } from "drizzle-orm"; import response from "@server/lib/response"; @@ -92,6 +92,7 @@ export async function acceptInvite( } let roleId: number; + let totalUsers: UserOrg[] | undefined; // get the role to make sure it exists const existingRole = await db .select() @@ -122,6 +123,12 @@ export async function acceptInvite( await trx .delete(userInvites) .where(eq(userInvites.inviteId, inviteId)); + + // Get the total number of users in the org now + totalUsers = await db + .select() + .from(userOrgs) + .where(eq(userOrgs.orgId, existingInvite.orgId)); }); return response(res, { diff --git a/server/routers/user/createOrgUser.ts b/server/routers/user/createOrgUser.ts index 264ea3d9..4419772a 100644 --- a/server/routers/user/createOrgUser.ts +++ b/server/routers/user/createOrgUser.ts @@ -6,7 +6,7 @@ import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; -import { db } from "@server/db"; +import { db, UserOrg } from "@server/db"; import { and, eq } from "drizzle-orm"; import { idp, idpOidcConfig, roles, userOrgs, users } from "@server/db"; import { generateId } from "@server/auth/sessions/app"; @@ -135,65 +135,76 @@ export async function createOrgUser( ); } - const [existingUser] = await db - .select() - .from(users) - .where(eq(users.username, username)); + let orgUsers: UserOrg[] | undefined; - if (existingUser) { - const [existingOrgUser] = await db + await db.transaction(async (trx) => { + const [existingUser] = await trx .select() - .from(userOrgs) - .where( - and( - eq(userOrgs.orgId, orgId), - eq(userOrgs.userId, existingUser.userId) - ) - ); + .from(users) + .where(eq(users.username, username)); - if (existingOrgUser) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "User already exists in this organization" - ) - ); + if (existingUser) { + const [existingOrgUser] = await trx + .select() + .from(userOrgs) + .where( + and( + eq(userOrgs.orgId, orgId), + eq(userOrgs.userId, existingUser.userId) + ) + ); + + if (existingOrgUser) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "User already exists in this organization" + ) + ); + } + + await trx + .insert(userOrgs) + .values({ + orgId, + userId: existingUser.userId, + roleId: role.roleId + }) + .returning(); + } else { + const userId = generateId(15); + + const [newUser] = await trx + .insert(users) + .values({ + userId: userId, + email, + username, + name, + type: "oidc", + idpId, + dateCreated: new Date().toISOString(), + emailVerified: true + }) + .returning(); + + await trx + .insert(userOrgs) + .values({ + orgId, + userId: newUser.userId, + roleId: role.roleId + }) + .returning(); } - await db - .insert(userOrgs) - .values({ - orgId, - userId: existingUser.userId, - roleId: role.roleId - }) - .returning(); - } else { - const userId = generateId(15); + // List all of the users in the org + orgUsers = await trx + .select() + .from(userOrgs) + .where(eq(userOrgs.orgId, orgId)); + }); - const [newUser] = await db - .insert(users) - .values({ - userId: userId, - email, - username, - name, - type: "oidc", - idpId, - dateCreated: new Date().toISOString(), - emailVerified: true - }) - .returning(); - - await db - .insert(userOrgs) - .values({ - orgId, - userId: newUser.userId, - roleId: role.roleId - }) - .returning(); - } } else { return next( createHttpError(HttpCode.BAD_REQUEST, "User type is required") diff --git a/server/routers/user/inviteUser.ts b/server/routers/user/inviteUser.ts index 5b2e8d1e..837ef179 100644 --- a/server/routers/user/inviteUser.ts +++ b/server/routers/user/inviteUser.ts @@ -99,6 +99,7 @@ export async function inviteUser( regenerate } = parsedBody.data; + // Check if the organization exists const org = await db .select() diff --git a/server/routers/user/removeUserOrg.ts b/server/routers/user/removeUserOrg.ts index 6e57a218..dcd8c6f2 100644 --- a/server/routers/user/removeUserOrg.ts +++ b/server/routers/user/removeUserOrg.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, resources, sites } from "@server/db"; +import { db, resources, sites, UserOrg } from "@server/db"; import { userOrgs, userResources, users, userSites } from "@server/db"; import { and, eq, exists } from "drizzle-orm"; import response from "@server/lib/response"; @@ -65,6 +65,8 @@ export async function removeUserOrg( ); } + let userCount: UserOrg[] | undefined; + await db.transaction(async (trx) => { await trx .delete(userOrgs) @@ -108,6 +110,11 @@ export async function removeUserOrg( ) ) ); + + userCount = await trx + .select() + .from(userOrgs) + .where(eq(userOrgs.orgId, orgId)); }); return response(res, { diff --git a/src/app/[orgId]/settings/domains/CreateDomainForm.tsx b/src/app/[orgId]/settings/domains/CreateDomainForm.tsx new file mode 100644 index 00000000..41f585d0 --- /dev/null +++ b/src/app/[orgId]/settings/domains/CreateDomainForm.tsx @@ -0,0 +1,430 @@ +"use client"; + +import { Button } from "@app/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from "@app/components/ui/form"; +import { Input } from "@app/components/ui/input"; +import { useToast } from "@app/hooks/useToast"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { + Credenza, + CredenzaBody, + CredenzaClose, + CredenzaContent, + CredenzaDescription, + CredenzaFooter, + CredenzaHeader, + CredenzaTitle +} from "@app/components/Credenza"; +import { createApiClient } from "@app/lib/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { useTranslations } from "next-intl"; +import { formatAxiosError } from "@app/lib/api"; +import { CreateDomainResponse } from "@server/routers/domain/createOrgDomain"; +import { StrategySelect } from "@app/components/StrategySelect"; +import { AxiosResponse } from "axios"; +import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; +import { InfoIcon, AlertTriangle } from "lucide-react"; +import CopyToClipboard from "@app/components/CopyToClipboard"; +import { + InfoSection, + InfoSectionContent, + InfoSections, + InfoSectionTitle +} from "@app/components/InfoSection"; +import { useOrgContext } from "@app/hooks/useOrgContext"; + +const formSchema = z.object({ + baseDomain: z.string().min(1, "Domain is required"), + type: z.enum(["ns", "cname"]) +}); + +type FormValues = z.infer; + +type CreateDomainFormProps = { + open: boolean; + setOpen: (open: boolean) => void; + onCreated?: (domain: CreateDomainResponse) => void; +}; + +export default function CreateDomainForm({ + open, + setOpen, + onCreated +}: CreateDomainFormProps) { + const [loading, setLoading] = useState(false); + const [createdDomain, setCreatedDomain] = + useState(null); + const api = createApiClient(useEnvContext()); + const t = useTranslations(); + const { toast } = useToast(); + const { org } = useOrgContext(); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + baseDomain: "", + type: "ns" + } + }); + + function reset() { + form.reset(); + setLoading(false); + setCreatedDomain(null); + } + + async function onSubmit(values: FormValues) { + setLoading(true); + try { + const response = await api.put>( + `/org/${org.org.orgId}/domain`, + values + ); + const domainData = response.data.data; + setCreatedDomain(domainData); + toast({ + title: t("success"), + description: t("domainCreatedDescription") + }); + onCreated?.(domainData); + } catch (e) { + toast({ + title: t("error"), + description: formatAxiosError(e), + variant: "destructive" + }); + } finally { + setLoading(false); + } + } + + const domainType = form.watch("type"); + const baseDomain = form.watch("baseDomain"); + + return ( + { + setOpen(val); + reset(); + }} + > + + + {t("domainAdd")} + + {t("domainAddDescription")} + + + + {!createdDomain ? ( +
+ + ( + + + + + )} + /> + ( + + {t("domain")} + + + + + + )} + /> + + + ) : ( +
+ + + + Add DNS Records + + + Add the following DNS records to your domain + provider to complete the setup. + + + +
+ {domainType === "ns" && + createdDomain.nsRecords && ( +
+

+ NS Records +

+ + + + Record + + +
+
+ + Type: + + + NS + +
+
+ + Name: + + + {baseDomain} + +
+ + Value: + + {createdDomain.nsRecords.map( + ( + nsRecord, + index + ) => ( +
+ +
+ ) + )} +
+
+
+
+
+ )} + + {domainType === "cname" && ( + <> + {createdDomain.cnameRecords && + createdDomain.cnameRecords.length > + 0 && ( +
+

+ CNAME Records +

+ + {createdDomain.cnameRecords.map( + ( + cnameRecord, + index + ) => ( + + + Record{" "} + {index + + 1} + + +
+
+ + Type: + + + CNAME + +
+
+ + Name: + + + { + cnameRecord.baseDomain + } + +
+
+ + Value: + + +
+
+
+
+ ) + )} +
+
+ )} + + {createdDomain.txtRecords && + createdDomain.txtRecords.length > + 0 && ( +
+

+ TXT Records +

+ + {createdDomain.txtRecords.map( + ( + txtRecord, + index + ) => ( + + + Record{" "} + {index + + 1} + + +
+
+ + Type: + + + TXT + +
+
+ + Name: + + + { + txtRecord.baseDomain + } + +
+
+ + Value: + + +
+
+
+
+ ) + )} +
+
+ )} + + )} +
+ + + + + Save These Records + + + Make sure to save these DNS records as you + will not see them again. + + + + + + + DNS Propagation + + + DNS changes may take some time to propagate + across the internet. This can take anywhere + from a few minutes to 48 hours, depending on + your DNS provider and TTL settings. + + +
+ )} +
+ + + + + {!createdDomain && ( + + )} + +
+
+ ); +} diff --git a/src/app/[orgId]/settings/domains/DomainsDataTable.tsx b/src/app/[orgId]/settings/domains/DomainsDataTable.tsx new file mode 100644 index 00000000..2008f0e8 --- /dev/null +++ b/src/app/[orgId]/settings/domains/DomainsDataTable.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; +import { DataTable } from "@app/components/ui/data-table"; +import { useTranslations } from "next-intl"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + onAdd?: () => void; + onRefresh?: () => void; + isRefreshing?: boolean; +} + +export function DomainsDataTable({ + columns, + data, + onAdd, + onRefresh, + isRefreshing +}: DataTableProps) { + const t = useTranslations(); + + return ( + + ); +} diff --git a/src/app/[orgId]/settings/domains/DomainsTable.tsx b/src/app/[orgId]/settings/domains/DomainsTable.tsx new file mode 100644 index 00000000..02cec6f2 --- /dev/null +++ b/src/app/[orgId]/settings/domains/DomainsTable.tsx @@ -0,0 +1,261 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; +import { DomainsDataTable } from "./DomainsDataTable"; +import { Button } from "@app/components/ui/button"; +import { ArrowUpDown } from "lucide-react"; +import { useState } from "react"; +import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; +import { formatAxiosError } from "@app/lib/api"; +import { createApiClient } from "@app/lib/api"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { Badge } from "@app/components/ui/badge"; +import { useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import CreateDomainForm from "./CreateDomainForm"; +import { useToast } from "@app/hooks/useToast"; +import { useOrgContext } from "@app/hooks/useOrgContext"; + +export type DomainRow = { + domainId: string; + baseDomain: string; + type: string; + verified: boolean; + failed: boolean; + tries: number; +}; + +type Props = { + domains: DomainRow[]; +}; + +export default function DomainsTable({ domains }: Props) { + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + const [selectedDomain, setSelectedDomain] = useState( + null + ); + const [isRefreshing, setIsRefreshing] = useState(false); + const [restartingDomains, setRestartingDomains] = useState>(new Set()); + const api = createApiClient(useEnvContext()); + const router = useRouter(); + const t = useTranslations(); + const { toast } = useToast(); + const { org } = useOrgContext(); + + const refreshData = async () => { + setIsRefreshing(true); + try { + router.refresh(); + } catch (error) { + toast({ + title: t("error"), + description: t("refreshError"), + variant: "destructive" + }); + } finally { + setIsRefreshing(false); + } + }; + + const deleteDomain = async (domainId: string) => { + try { + await api.delete(`/org/${org.org.orgId}/domain/${domainId}`); + toast({ + title: t("success"), + description: t("domainDeletedDescription") + }); + setIsDeleteModalOpen(false); + refreshData(); + } catch (e) { + toast({ + title: t("error"), + description: formatAxiosError(e), + variant: "destructive" + }); + } + }; + + const restartDomain = async (domainId: string) => { + setRestartingDomains(prev => new Set(prev).add(domainId)); + try { + await api.post(`/org/${org.org.orgId}/domain/${domainId}/restart`); + toast({ + title: t("success"), + description: t("domainRestartedDescription", { fallback: "Domain verification restarted successfully" }) + }); + refreshData(); + } catch (e) { + toast({ + title: t("error"), + description: formatAxiosError(e), + variant: "destructive" + }); + } finally { + setRestartingDomains(prev => { + const newSet = new Set(prev); + newSet.delete(domainId); + return newSet; + }); + } + }; + + const getTypeDisplay = (type: string) => { + switch (type) { + case "ns": + return t("selectDomainTypeNsName"); + case "cname": + return t("selectDomainTypeCnameName"); + default: + return type; + } + }; + + const columns: ColumnDef[] = [ + { + accessorKey: "baseDomain", + header: ({ column }) => { + return ( + + ); + } + }, + { + accessorKey: "type", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + const type = row.original.type; + return ( + {getTypeDisplay(type)} + ); + } + }, + { + accessorKey: "verified", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + const { verified, failed } = row.original; + if (verified) { + return {t("verified")}; + } else if (failed) { + return {t("failed", { fallback: "Failed" })}; + } else { + return {t("pending")}; + } + } + }, + { + id: "actions", + cell: ({ row }) => { + const domain = row.original; + const isRestarting = restartingDomains.has(domain.domainId); + + return ( +
+ {domain.failed && ( + + )} + +
+ ); + } + } + ]; + + return ( + <> + {selectedDomain && ( + { + setIsDeleteModalOpen(val); + setSelectedDomain(null); + }} + dialog={ +
+

+ {t("domainQuestionRemove", { + domain: selectedDomain.baseDomain + })} +

+

+ {t("domainMessageRemove")} +

+

{t("domainMessageConfirm")}

+
+ } + buttonText={t("domainConfirmDelete")} + onConfirm={async () => + deleteDomain(selectedDomain.domainId) + } + string={selectedDomain.baseDomain} + title={t("domainDelete")} + /> + )} + + { + refreshData(); + }} + /> + + setIsCreateModalOpen(true)} + onRefresh={refreshData} + isRefreshing={isRefreshing} + /> + + ); +} diff --git a/src/app/[orgId]/settings/domains/page.tsx b/src/app/[orgId]/settings/domains/page.tsx new file mode 100644 index 00000000..d20e431f --- /dev/null +++ b/src/app/[orgId]/settings/domains/page.tsx @@ -0,0 +1,60 @@ +import { internal } from "@app/lib/api"; +import { authCookieHeader } from "@app/lib/api/cookies"; +import { AxiosResponse } from "axios"; +import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; +import DomainsTable, { DomainRow } from "./DomainsTable"; +import { getTranslations } from "next-intl/server"; +import { cache } from "react"; +import { GetOrgResponse } from "@server/routers/org"; +import { redirect } from "next/navigation"; +import OrgProvider from "@app/providers/OrgProvider"; +import { ListDomainsResponse } from "@server/routers/domain"; + +type Props = { + params: Promise<{ orgId: string }>; +}; + +export default async function DomainsPage(props: Props) { + const params = await props.params; + + let domains: DomainRow[] = []; + try { + const res = await internal.get< + AxiosResponse + >(`/org/${params.orgId}/domains`, await authCookieHeader()); + domains = res.data.data.domains as DomainRow[]; + } catch (e) { + console.error(e); + } + + let org = null; + try { + const getOrg = cache(async () => + internal.get>( + `/org/${params.orgId}`, + await authCookieHeader() + ) + ); + const res = await getOrg(); + org = res.data.data; + } catch { + redirect(`/${params.orgId}`); + } + + if (!org) { + } + + const t = await getTranslations(); + + return ( + <> + + + + + + ); +} diff --git a/src/app/[orgId]/settings/general/page.tsx b/src/app/[orgId]/settings/general/page.tsx index 62e371db..eb08ed80 100644 --- a/src/app/[orgId]/settings/general/page.tsx +++ b/src/app/[orgId]/settings/general/page.tsx @@ -202,25 +202,25 @@ export default function GeneralPage() { )} /> - ( - - Subnet - - - - - - The subnet for this organization's network configuration. - - - )} - /> + {/* ( */} + {/* */} + {/* Subnet */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* The subnet for this organization's network configuration. */} + {/* */} + {/* */} + {/* )} */} + {/* /> */} diff --git a/src/app/[orgId]/settings/layout.tsx b/src/app/[orgId]/settings/layout.tsx index e18a3bd5..b28beb4f 100644 --- a/src/app/[orgId]/settings/layout.tsx +++ b/src/app/[orgId]/settings/layout.tsx @@ -1,6 +1,7 @@ import { Metadata } from "next"; import { Combine, + KeyRound, LinkIcon, Settings, Users, @@ -11,6 +12,7 @@ import { verifySession } from "@app/lib/auth/verifySession"; import { redirect } from "next/navigation"; import { internal } from "@app/lib/api"; import { AxiosResponse } from "axios"; +import { ListOrgsResponse } from "@server/routers/org"; import { GetOrgResponse, ListUserOrgsResponse } from "@server/routers/org"; import { authCookieHeader } from "@app/lib/api/cookies"; import { cache } from "react"; diff --git a/src/app/[orgId]/settings/resources/ResourcesTable.tsx b/src/app/[orgId]/settings/resources/ResourcesTable.tsx index df176331..3d2d8710 100644 --- a/src/app/[orgId]/settings/resources/ResourcesTable.tsx +++ b/src/app/[orgId]/settings/resources/ResourcesTable.tsx @@ -32,6 +32,8 @@ import { Switch } from "@app/components/ui/switch"; import { AxiosResponse } from "axios"; import { UpdateResourceResponse } from "@server/routers/resource"; import { useTranslations } from "next-intl"; +import { InfoPopup } from "@app/components/ui/info-popup"; +import { Badge } from "@app/components/ui/badge"; export type ResourceRow = { id: number; @@ -45,6 +47,7 @@ export type ResourceRow = { protocol: string; proxyPort: number | null; enabled: boolean; + domainId?: string; }; type ResourcesTableProps = { @@ -158,17 +161,26 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) { cell: ({ row }) => { const resourceRow = row.original; return ( -
- {!resourceRow.http ? ( - + {!resourceRow.domainId ? ( + ) : ( - +
+ {!resourceRow.http ? ( + + ) : ( + + )} +
)}
); @@ -215,7 +227,10 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) { header: t("enabled"), cell: ({ row }) => ( toggleResourceEnabled(val, row.original.id) } @@ -261,7 +276,11 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) { - diff --git a/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx b/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx index 7ccc5e50..717e4d49 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx @@ -10,10 +10,14 @@ import { InfoSections, InfoSectionTitle } from "@app/components/InfoSection"; -import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useDockerSocket } from "@app/hooks/useDockerSocket"; import { useTranslations } from "next-intl"; +import { AxiosResponse } from "axios"; +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { RotateCw } from "lucide-react"; +import { createApiClient } from "@app/lib/api"; type ResourceInfoBoxType = {}; diff --git a/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx index 4d7cd653..c8f6255c 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx @@ -205,10 +205,10 @@ export default function ResourceAuthenticationPage() { console.error(e); toast({ variant: "destructive", - title: t('resourceErrorAuthFetch'), + title: t("resourceErrorAuthFetch"), description: formatAxiosError( e, - t('resourceErrorAuthFetchDescription') + t("resourceErrorAuthFetchDescription") ) }); } @@ -235,18 +235,18 @@ export default function ResourceAuthenticationPage() { }); toast({ - title: t('resourceWhitelistSave'), - description: t('resourceWhitelistSaveDescription') + title: t("resourceWhitelistSave"), + description: t("resourceWhitelistSaveDescription") }); router.refresh(); } catch (e) { console.error(e); toast({ variant: "destructive", - title: t('resourceErrorWhitelistSave'), + title: t("resourceErrorWhitelistSave"), description: formatAxiosError( e, - t('resourceErrorWhitelistSaveDescription') + t("resourceErrorWhitelistSaveDescription") ) }); } finally { @@ -283,18 +283,18 @@ export default function ResourceAuthenticationPage() { }); toast({ - title: t('resourceAuthSettingsSave'), - description: t('resourceAuthSettingsSaveDescription') + title: t("resourceAuthSettingsSave"), + description: t("resourceAuthSettingsSaveDescription") }); router.refresh(); } catch (e) { console.error(e); toast({ variant: "destructive", - title: t('resourceErrorUsersRolesSave'), + title: t("resourceErrorUsersRolesSave"), description: formatAxiosError( e, - t('resourceErrorUsersRolesSaveDescription') + t("resourceErrorUsersRolesSaveDescription") ) }); } finally { @@ -310,8 +310,8 @@ export default function ResourceAuthenticationPage() { }) .then(() => { toast({ - title: t('resourcePasswordRemove'), - description: t('resourcePasswordRemoveDescription') + title: t("resourcePasswordRemove"), + description: t("resourcePasswordRemoveDescription") }); updateAuthInfo({ @@ -322,10 +322,10 @@ export default function ResourceAuthenticationPage() { .catch((e) => { toast({ variant: "destructive", - title: t('resourceErrorPasswordRemove'), + title: t("resourceErrorPasswordRemove"), description: formatAxiosError( e, - t('resourceErrorPasswordRemoveDescription') + t("resourceErrorPasswordRemoveDescription") ) }); }) @@ -340,8 +340,8 @@ export default function ResourceAuthenticationPage() { }) .then(() => { toast({ - title: t('resourcePincodeRemove'), - description: t('resourcePincodeRemoveDescription') + title: t("resourcePincodeRemove"), + description: t("resourcePincodeRemoveDescription") }); updateAuthInfo({ @@ -352,10 +352,10 @@ export default function ResourceAuthenticationPage() { .catch((e) => { toast({ variant: "destructive", - title: t('resourceErrorPincodeRemove'), + title: t("resourceErrorPincodeRemove"), description: formatAxiosError( e, - t('resourceErrorPincodeRemoveDescription') + t("resourceErrorPincodeRemoveDescription") ) }); }) @@ -400,140 +400,151 @@ export default function ResourceAuthenticationPage() { - {t('resourceUsersRoles')} + {t("resourceUsersRoles")} - {t('resourceUsersRolesDescription')} + {t("resourceUsersRolesDescription")} - setSsoEnabled(val)} - /> + + setSsoEnabled(val)} + /> -
- - {ssoEnabled && ( - <> - ( - - {t('roles')} - - { - usersRolesForm.setValue( - "roles", - newRoles as [ - Tag, - ...Tag[] - ] - ); - }} - enableAutocomplete={ - true - } - autocompleteOptions={ - allRoles - } - allowDuplicates={ - false - } - restrictTagsToAutocompleteOptions={ - true - } - sortTags={true} - /> - - - - {t('resourceRoleDescription')} - - - )} - /> - ( - - {t('users')} - - { - usersRolesForm.setValue( - "users", - newUsers as [ - Tag, - ...Tag[] - ] - ); - }} - enableAutocomplete={ - true - } - autocompleteOptions={ - allUsers - } - allowDuplicates={ - false - } - restrictTagsToAutocompleteOptions={ - true - } - sortTags={true} - /> - - - - )} - /> - - )} - - +
+ + {ssoEnabled && ( + <> + ( + + + {t("roles")} + + + { + usersRolesForm.setValue( + "roles", + newRoles as [ + Tag, + ...Tag[] + ] + ); + }} + enableAutocomplete={ + true + } + autocompleteOptions={ + allRoles + } + allowDuplicates={ + false + } + restrictTagsToAutocompleteOptions={ + true + } + sortTags={true} + /> + + + + {t( + "resourceRoleDescription" + )} + + + )} + /> + ( + + + {t("users")} + + + { + usersRolesForm.setValue( + "users", + newUsers as [ + Tag, + ...Tag[] + ] + ); + }} + enableAutocomplete={ + true + } + autocompleteOptions={ + allUsers + } + allowDuplicates={ + false + } + restrictTagsToAutocompleteOptions={ + true + } + sortTags={true} + /> + + + + )} + /> + + )} + + +
@@ -550,170 +561,195 @@ export default function ResourceAuthenticationPage() { - {t('resourceAuthMethods')} + {t("resourceAuthMethods")} - {t('resourceAuthMethodsDescriptions')} + {t("resourceAuthMethodsDescriptions")} - {/* Password Protection */} -
-
- - - {t('resourcePasswordProtection', {status: authInfo.password? t('enabled') : t('disabled')})} - + + {/* Password Protection */} +
+
+ + + {t("resourcePasswordProtection", { + status: authInfo.password + ? t("enabled") + : t("disabled") + })} + +
+
- -
- {/* PIN Code Protection */} -
-
- - - {t('resourcePincodeProtection', {status: authInfo.pincode ? t('enabled') : t('disabled')})} - + {/* PIN Code Protection */} +
+
+ + + {t("resourcePincodeProtection", { + status: authInfo.pincode + ? t("enabled") + : t("disabled") + })} + +
+
- -
+ - {t('otpEmailTitle')} + {t("otpEmailTitle")} - {t('otpEmailTitleDescription')} + {t("otpEmailTitleDescription")} - {!env.email.emailEnabled && ( - - - - {t('otpEmailSmtpRequired')} - - - {t('otpEmailSmtpRequiredDescription')} - - - )} - + + {!env.email.emailEnabled && ( + + + + {t("otpEmailSmtpRequired")} + + + {t("otpEmailSmtpRequiredDescription")} + + + )} + - {whitelistEnabled && env.email.emailEnabled && ( -
- - ( - - - - - - {/* @ts-ignore */} - { - return z - .string() - .email() - .or( - z - .string() - .regex( - /^\*@[\w.-]+\.[a-zA-Z]{2,}$/, - { - message: t('otpEmailErrorInvalid') - } - ) - ) - .safeParse( - tag - ).success; - }} - setActiveTagIndex={ - setActiveEmailTagIndex - } - placeholder={t('otpEmailEnter')} - tags={ - whitelistForm.getValues() - .emails - } - setTags={( - newRoles - ) => { - whitelistForm.setValue( - "emails", - newRoles as [ - Tag, - ...Tag[] - ] - ); - }} - allowDuplicates={ - false - } - sortTags={true} - /> - - - {t('otpEmailEnterDescription')} - - - )} - /> - - - )} + {whitelistEnabled && env.email.emailEnabled && ( +
+ + ( + + + + + + {/* @ts-ignore */} + { + return z + .string() + .email() + .or( + z + .string() + .regex( + /^\*@[\w.-]+\.[a-zA-Z]{2,}$/, + { + message: + t( + "otpEmailErrorInvalid" + ) + } + ) + ) + .safeParse( + tag + ).success; + }} + setActiveTagIndex={ + setActiveEmailTagIndex + } + placeholder={t( + "otpEmailEnter" + )} + tags={ + whitelistForm.getValues() + .emails + } + setTags={( + newRoles + ) => { + whitelistForm.setValue( + "emails", + newRoles as [ + Tag, + ...Tag[] + ] + ); + }} + allowDuplicates={ + false + } + sortTags={true} + /> + + + {t( + "otpEmailEnterDescription" + )} + + + )} + /> + + + )} +
diff --git a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx index cdff0375..df04ec2a 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx @@ -66,6 +66,18 @@ import { } from "@server/routers/resource"; import { SwitchInput } from "@app/components/SwitchInput"; import { useTranslations } from "next-intl"; +import { + Credenza, + CredenzaBody, + CredenzaClose, + CredenzaContent, + CredenzaDescription, + CredenzaFooter, + CredenzaHeader, + CredenzaTitle +} from "@app/components/Credenza"; +import DomainPicker from "@app/components/DomainPicker"; +import { Globe } from "lucide-react"; const TransferFormSchema = z.object({ siteId: z.number() @@ -80,6 +92,7 @@ export default function GeneralForm() { const { org } = useOrgContext(); const router = useRouter(); const t = useTranslations(); + const [editDomainOpen, setEditDomainOpen] = useState(false); const { env } = useEnvContext(); @@ -99,46 +112,22 @@ export default function GeneralForm() { const [domainType, setDomainType] = useState<"subdomain" | "basedomain">( resource.isBaseDomain ? "basedomain" : "subdomain" ); + const [resourceFullDomain, setResourceFullDomain] = useState( + `${resource.ssl ? "https" : "http"}://${resource.fullDomain}` + ); + const [selectedDomain, setSelectedDomain] = useState<{ + domainId: string; + subdomain?: string; + fullDomain: string; + } | null>(null); const GeneralFormSchema = z .object({ enabled: z.boolean(), subdomain: z.string().optional(), name: z.string().min(1).max(255), - proxyPort: z.number().optional(), - http: z.boolean(), - isBaseDomain: z.boolean().optional(), domainId: z.string().optional() - }) - .refine( - (data) => { - if (!data.http) { - return z - .number() - .int() - .min(1) - .max(65535) - .safeParse(data.proxyPort).success; - } - return true; - }, - { - message: t("proxyErrorInvalidPort"), - path: ["proxyPort"] - } - ) - .refine( - (data) => { - if (data.http && !data.isBaseDomain) { - return subdomainSchema.safeParse(data.subdomain).success; - } - return true; - }, - { - message: t("subdomainErrorInvalid"), - path: ["subdomain"] - } - ); + }); type GeneralFormValues = z.infer; @@ -148,9 +137,6 @@ export default function GeneralForm() { enabled: resource.enabled, name: resource.name, subdomain: resource.subdomain ? resource.subdomain : undefined, - proxyPort: resource.proxyPort ? resource.proxyPort : undefined, - http: resource.http, - isBaseDomain: resource.isBaseDomain ? true : false, domainId: resource.domainId || undefined }, mode: "onChange" @@ -213,10 +199,8 @@ export default function GeneralForm() { { enabled: data.enabled, name: data.name, - subdomain: data.http ? data.subdomain : undefined, - proxyPort: data.proxyPort, - isBaseDomain: data.http ? data.isBaseDomain : undefined, - domainId: data.http ? data.domainId : undefined + subdomain: data.subdomain, + domainId: data.domainId, } ) .catch((e) => { @@ -242,8 +226,6 @@ export default function GeneralForm() { enabled: data.enabled, name: data.name, subdomain: data.subdomain, - proxyPort: data.proxyPort, - isBaseDomain: data.isBaseDomain, fullDomain: resource.fullDomain }); @@ -288,449 +270,290 @@ export default function GeneralForm() { return ( !loadingPage && ( - - - - - {t("resourceGeneral")} - - - {t("resourceGeneralDescription")} - - + <> + + + + + {t("resourceGeneral")} + + + {t("resourceGeneralDescription")} + + - - -
- - ( - -
- - form.setValue("enabled", val)} - /> - -
- - {t("resourceEnable")} - - - {t("resourceVisibilityTitleDescription")} - -
-
- -
- )} - /> - - ( - - - {t("name")} - - - - - - - )} - /> - - {resource.http && ( - <> - {env.flags - .allowBaseDomainResources && ( - ( - - - {t( - "domainType" - )} - - - - - )} - /> - )} - -
- {domainType === "subdomain" ? ( -
- - {t("subdomain")} - -
-
- ( - - - - - - - )} - /> -
-
- ( - - - - - )} - /> -
-
-
- ) : ( - ( - - - {t( - "baseDomain" - )} - - - - - )} - /> - )} -
- - )} - - {!resource.http && ( + + + + ( + +
+ + + form.setValue( + "enabled", + val + ) + } + /> + +
+ +
+ )} + /> + + ( - {t( - "resourcePortNumber" - )} + {t("name")} - - field.onChange( - e.target - .value - ? parseInt( - e - .target - .value - ) - : null - ) - } - /> + )} /> - )} - - -
-
- - - -
- - - - - {t("resourceTransfer")} - - - {t("resourceTransferDescription")} - - - - - -
- - ( - - - {t("siteDestination")} - - - - - - - - - - - - {t( - "sitesNotFound" - )} - - - {sites.map( - (site) => ( - { - transferForm.setValue( - "siteId", - site.siteId - ); - setOpen( - false - ); - }} - > - { - site.name - } - - - ) - )} - - - - - - + {resource.http && ( +
+ +
+ + + {resourceFullDomain} + + +
+
)} - /> - - -
-
+ + + + - - - -
-
+ + + +
+ + + + + {t("resourceTransfer")} + + + {t("resourceTransferDescription")} + + + + + +
+ + ( + + + {t("siteDestination")} + + + + + + + + + + + + {t( + "sitesNotFound" + )} + + + {sites.map( + ( + site + ) => ( + { + transferForm.setValue( + "siteId", + site.siteId + ); + setOpen( + false + ); + }} + > + { + site.name + } + + + ) + )} + + + + + + + )} + /> + + +
+
+ + + + +
+
+ + setEditDomainOpen(setOpen)} + > + + + Edit Domain + + Select a domain for your resource + + + + { + const selected = { + domainId: res.domainId, + subdomain: res.subdomain, + fullDomain: res.fullDomain + }; + setSelectedDomain(selected); + }} + /> + + + + + + + + + + ) ); } diff --git a/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx index 9d78833d..6695585a 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx @@ -772,81 +772,50 @@ export default function ReverseProxyTargets(props: { className="space-y-4" id="tls-settings-form" > + ( + + + { + field.onChange(val); + }} + /> + + + )} + /> ( + + {t("targetTlsSni")} + - { - field.onChange(val); - }} - /> + + + {t( + "targetTlsSniDescription" + )} + + )} /> - -
- - - -
- - ( - - - {t("targetTlsSni")} - - - - - - {t( - "targetTlsSniDescription" - )} - - - - )} - /> - -
diff --git a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx index 316e9cfe..2f7d03ee 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/rules/page.tsx @@ -247,7 +247,7 @@ export default function ResourceRules(props: { async function saveAllSettings() { try { setLoading(true); - + // Save rules enabled state const res = await api .post(`/resource/${params.resourceId}`, { @@ -594,17 +594,10 @@ export default function ResourceRules(props: {
setRulesEnabled(val)} /> -
- -

- {t('rulesEnableDescription')} -

-
diff --git a/src/app/[orgId]/settings/resources/create/page.tsx b/src/app/[orgId]/settings/resources/create/page.tsx index bbcafe68..b357e03f 100644 --- a/src/app/[orgId]/settings/resources/create/page.tsx +++ b/src/app/[orgId]/settings/resources/create/page.tsx @@ -63,6 +63,7 @@ import { SquareArrowOutUpRight } from "lucide-react"; import CopyTextBox from "@app/components/CopyTextBox"; import Link from "next/link"; import { useTranslations } from "next-intl"; +import DomainPicker from "@app/components/DomainPicker"; const baseResourceFormSchema = z.object({ name: z.string().min(1).max(255), @@ -70,17 +71,10 @@ const baseResourceFormSchema = z.object({ http: z.boolean() }); -const httpResourceFormSchema = z.discriminatedUnion("isBaseDomain", [ - z.object({ - isBaseDomain: z.literal(true), - domainId: z.string().min(1) - }), - z.object({ - isBaseDomain: z.literal(false), - domainId: z.string().min(1), - subdomain: z.string().pipe(subdomainSchema) - }) -]); +const httpResourceFormSchema = z.object({ + domainId: z.string().optional(), + subdomain: z.string().optional() +}); const tcpUdpResourceFormSchema = z.object({ protocol: z.string(), @@ -143,11 +137,7 @@ export default function Page() { const httpForm = useForm({ resolver: zodResolver(httpResourceFormSchema), - defaultValues: { - subdomain: "", - domainId: "", - isBaseDomain: false - } + defaultValues: {} }); const tcpUdpForm = useForm({ @@ -173,20 +163,10 @@ export default function Page() { if (isHttp) { const httpData = httpForm.getValues(); - if (httpData.isBaseDomain) { - Object.assign(payload, { - domainId: httpData.domainId, - isBaseDomain: true, - protocol: "tcp" - }); - } else { Object.assign(payload, { subdomain: httpData.subdomain, - domainId: httpData.domainId, - isBaseDomain: false, - protocol: "tcp" + domainId: httpData.domainId }); - } } else { const tcpUdpData = tcpUdpForm.getValues(); Object.assign(payload, { @@ -498,218 +478,23 @@ export default function Page() { - - - - {env.flags - .allowBaseDomainResources && ( - ( - - - {t( - "domainType" - )} - - - - - )} - /> - )} - - {!httpForm.watch( - "isBaseDomain" - ) && ( - - - {t("subdomain")} - -
-
- ( - - - - - - - )} - /> -
-
- ( - - - - - )} - /> -
-
- - {t( - "subdomnainDescription" - )} - -
- )} - - {httpForm.watch( - "isBaseDomain" - ) && ( - ( - - - {t( - "baseDomain" - )} - - - - - )} - /> - )} - - -
+ { + httpForm.setValue( + "subdomain", + res.subdomain + ); + httpForm.setValue( + "domainId", + res.domainId + ); + console.log( + "Domain changed:", + res + ); + }} + />
) : ( @@ -921,7 +706,7 @@ export default function Page() { type="button" onClick={() => router.push( - `/${orgId}/settings/resources/${resourceId}` + `/${orgId}/settings/resources/${resourceId}/proxy` ) } > diff --git a/src/app/[orgId]/settings/resources/page.tsx b/src/app/[orgId]/settings/resources/page.tsx index bbd2a582..371b4404 100644 --- a/src/app/[orgId]/settings/resources/page.tsx +++ b/src/app/[orgId]/settings/resources/page.tsx @@ -67,7 +67,8 @@ export default async function ResourcesPage(props: ResourcesPageProps) { resource.whitelist ? "protected" : "not_protected", - enabled: resource.enabled + enabled: resource.enabled, + domainId: resource.domainId || undefined }; }); diff --git a/src/app/[orgId]/settings/sites/SitesSplashCard.tsx b/src/app/[orgId]/settings/sites/SitesSplashCard.tsx index 7484a15c..8bab93e6 100644 --- a/src/app/[orgId]/settings/sites/SitesSplashCard.tsx +++ b/src/app/[orgId]/settings/sites/SitesSplashCard.tsx @@ -5,10 +5,12 @@ import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { ArrowRight, DockIcon as Docker, Globe, Server, X } from "lucide-react"; import Link from "next/link"; +import { useEnvContext } from "@app/hooks/useEnvContext"; import { useTranslations } from 'next-intl'; export const SitesSplashCard = () => { const [isDismissed, setIsDismissed] = useState(true); + const { env } = useEnvContext(); const key = "sites-splash-card-dismissed"; const t = useTranslations(); diff --git a/src/app/[orgId]/settings/sites/create/page.tsx b/src/app/[orgId]/settings/sites/create/page.tsx index 998b1f20..16b65916 100644 --- a/src/app/[orgId]/settings/sites/create/page.tsx +++ b/src/app/[orgId]/settings/sites/create/page.tsx @@ -375,7 +375,10 @@ WantedBy=default.target` async function onSubmit(data: CreateSiteFormValues) { setCreateLoading(true); - let payload: CreateSiteBody = { name: data.name, type: data.method }; + let payload: CreateSiteBody = { + name: data.name, + type: data.method as "newt" | "wireguard" | "local" + }; if (data.method == "wireguard") { if (!siteDefaults || !wgConfig) { @@ -412,7 +415,7 @@ WantedBy=default.target` exitNodeId: siteDefaults.exitNodeId, secret: siteDefaults.newtSecret, newtId: siteDefaults.newtId, - address: clientAddress + // address: clientAddress }; } @@ -573,42 +576,42 @@ WantedBy=default.target` )} /> - ( - - - Site Address - - - { - setClientAddress( - e.target - .value - ); - field.onChange( - e.target - .value - ); - }} - /> - - - - Specify the IP - address of the host. - - - )} - /> + {/* ( */} + {/* */} + {/* */} + {/* Site Address */} + {/* */} + {/* */} + {/* { */} + {/* setClientAddress( */} + {/* e.target */} + {/* .value */} + {/* ); */} + {/* field.onChange( */} + {/* e.target */} + {/* .value */} + {/* ); */} + {/* }} */} + {/* /> */} + {/* */} + {/* */} + {/* */} + {/* Specify the IP */} + {/* address of the host. */} + {/* */} + {/* */} + {/* )} */} + {/* /> */} @@ -760,7 +763,7 @@ WantedBy=default.target` ? "squareOutlinePrimary" : "squareOutline" } - className={`flex-1 min-w-[120px] ${platform === os ? "bg-primary/10" : ""}`} + className={`flex-1 min-w-[120px] ${platform === os ? "bg-primary/10" : ""} shadow-none`} onClick={() => { setPlatform(os); }} @@ -791,7 +794,7 @@ WantedBy=default.target` ? "squareOutlinePrimary" : "squareOutline" } - className={`flex-1 min-w-[120px] ${architecture === arch ? "bg-primary/10" : ""}`} + className={`flex-1 min-w-[120px] ${architecture === arch ? "bg-primary/10" : ""} shadow-none`} onClick={() => setArchitecture( arch diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index f02e6f7b..060f18ac 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -1,5 +1,6 @@ import { Metadata } from "next"; -import { Users } from "lucide-react"; +import { TopbarNav } from "@app/components/TopbarNav"; +import { KeyRound, Users } from "lucide-react"; import { verifySession } from "@app/lib/auth/verifySession"; import { redirect } from "next/navigation"; import { cache } from "react"; diff --git a/src/app/auth/layout.tsx b/src/app/auth/layout.tsx index 05a65fce..2b524e71 100644 --- a/src/app/auth/layout.tsx +++ b/src/app/auth/layout.tsx @@ -1,4 +1,5 @@ import ProfileIcon from "@app/components/ProfileIcon"; +import ThemeSwitcher from "@app/components/ThemeSwitcher"; import { Separator } from "@app/components/ui/separator"; import { priv } from "@app/lib/api"; import { verifySession } from "@app/lib/auth/verifySession"; @@ -11,7 +12,7 @@ import { cache } from "react"; import { getTranslations } from "next-intl/server"; export const metadata: Metadata = { - title: `Auth - Pangolin`, + title: `Auth - "Pangolin`, description: "" }; @@ -23,6 +24,7 @@ export default async function AuthLayout({ children }: AuthLayoutProps) { const getUser = cache(verifySession); const user = await getUser(); const t = await getTranslations(); + const hideFooter = true; const licenseStatusRes = await cache( async () => @@ -34,20 +36,18 @@ export default async function AuthLayout({ children }: AuthLayoutProps) { return (
- {user && ( - -
- -
-
- )} +
+ +
{children}
{!( - licenseStatus.isHostLicensed && licenseStatus.isLicenseValid + hideFooter || ( + licenseStatus.isHostLicensed && + licenseStatus.isLicenseValid) ) && (
@@ -73,7 +73,7 @@ export default async function AuthLayout({ children }: AuthLayoutProps) { aria-label="GitHub" className="flex items-center space-x-2 whitespace-nowrap" > - {t('communityEdition')} + {t("communityEdition")}

- {t('inviteAlready')} + {t("inviteAlready")}

- {t('inviteAlreadyDescription')} + {t("inviteAlreadyDescription")}

@@ -67,7 +67,7 @@ export default async function Page(props: { {(!signUpDisabled || isInvite) && (

- {t('authNoAccount')}{" "} + {t("authNoAccount")}{" "} - {t('signup')} + {t("signup")}

)} diff --git a/src/app/auth/reset-password/ResetPasswordForm.tsx b/src/app/auth/reset-password/ResetPasswordForm.tsx index 8262c738..596afb99 100644 --- a/src/app/auth/reset-password/ResetPasswordForm.tsx +++ b/src/app/auth/reset-password/ResetPasswordForm.tsx @@ -54,12 +54,14 @@ export type ResetPasswordFormProps = { emailParam?: string; tokenParam?: string; redirect?: string; + quickstart?: boolean; }; export default function ResetPasswordForm({ emailParam, tokenParam, - redirect + redirect, + quickstart }: ResetPasswordFormProps) { const router = useRouter(); @@ -184,17 +186,63 @@ export default function ResetPasswordForm({ return; } - setSuccessMessage(t('passwordResetSuccess')); + setSuccessMessage(quickstart ? t('accountSetupSuccess') : t('passwordResetSuccess')); - setTimeout(() => { - if (redirect) { - const safe = cleanRedirect(redirect); - router.push(safe); - } else { - router.push("/login"); + // Auto-login after successful password reset + try { + const loginRes = await api.post("/auth/login", { + email: form.getValues("email"), + password: form.getValues("password") + }); + + if (loginRes.data.data?.codeRequested) { + if (redirect) { + router.push(`/auth/login?redirect=${redirect}`); + } else { + router.push("/auth/login"); + } + return; } - setIsSubmitting(false); - }, 1500); + + if (loginRes.data.data?.emailVerificationRequired) { + try { + await api.post("/auth/verify-email/request"); + } catch (verificationError) { + console.error("Failed to send verification code:", verificationError); + } + + if (redirect) { + router.push(`/auth/verify-email?redirect=${redirect}`); + } else { + router.push("/auth/verify-email"); + } + return; + } + + // Login successful, redirect + setTimeout(() => { + if (redirect) { + const safe = cleanRedirect(redirect); + router.push(safe); + } else { + router.push("/"); + } + setIsSubmitting(false); + }, 1500); + + } catch (loginError) { + // Auto-login failed, but password reset was successful + console.error("Auto-login failed:", loginError); + setTimeout(() => { + if (redirect) { + const safe = cleanRedirect(redirect); + router.push(safe); + } else { + router.push("/login"); + } + setIsSubmitting(false); + }, 1500); + } } } @@ -202,9 +250,14 @@ export default function ResetPasswordForm({
- {t('passwordReset')} + + {quickstart ? t('completeAccountSetup') : t('passwordReset')} + - {t('passwordResetDescription')} + {quickstart + ? t('completeAccountSetupDescription') + : t('passwordResetDescription') + } @@ -229,7 +282,10 @@ export default function ResetPasswordForm({ - {t('passwordResetSent')} + {quickstart + ? t('accountSetupSent') + : t('passwordResetSent') + } )} @@ -269,7 +325,10 @@ export default function ResetPasswordForm({ render={({ field }) => ( - {t('passwordResetCode')} + {quickstart + ? t('accountSetupCode') + : t('passwordResetCode') + } - {t('passwordResetCodeDescription')} + {quickstart + ? t('accountSetupCodeDescription') + : t('passwordResetCodeDescription') + } )} @@ -292,7 +354,10 @@ export default function ResetPasswordForm({ render={({ field }) => ( - {t('passwordNew')} + {quickstart + ? t('passwordCreate') + : t('passwordNew') + } ( - {t('passwordNewConfirm')} + {quickstart + ? t('passwordCreateConfirm') + : t('passwordNewConfirm') + } )} {state === "reset" - ? t('passwordReset') + ? (quickstart ? t('completeSetup') : t('passwordReset')) : t('pincodeSubmit2')} )} @@ -422,7 +490,10 @@ export default function ResetPasswordForm({ {isSubmitting && ( )} - {t('passwordResetSubmit')} + {quickstart + ? t('accountSetupSubmit') + : t('passwordResetSubmit') + } )} diff --git a/src/app/auth/reset-password/page.tsx b/src/app/auth/reset-password/page.tsx index a0466208..f06c7c4c 100644 --- a/src/app/auth/reset-password/page.tsx +++ b/src/app/auth/reset-password/page.tsx @@ -13,6 +13,7 @@ export default async function Page(props: { redirect: string | undefined; email: string | undefined; token: string | undefined; + quickstart?: string | undefined; }>; }) { const searchParams = await props.searchParams; @@ -35,6 +36,9 @@ export default async function Page(props: { redirect={searchParams.redirect} tokenParam={searchParams.token} emailParam={searchParams.email} + quickstart={ + searchParams.quickstart === "true" ? true : undefined + } />

@@ -46,7 +50,7 @@ export default async function Page(props: { } className="underline" > - {t('loginBack')} + {t("loginBack")}

diff --git a/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx b/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx index 3c4e8023..bf703309 100644 --- a/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx +++ b/src/app/auth/resource/[resourceId]/ResourceAuthPortal.tsx @@ -43,6 +43,7 @@ import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import Link from "next/link"; +import Image from "next/image"; import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext"; import { useTranslations } from "next-intl"; @@ -185,8 +186,8 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { setOtpState("otp_sent"); submitOtpForm.setValue("email", values.email); toast({ - title: t('otpEmailSent'), - description: t('otpEmailSentDescription') + title: t("otpEmailSent"), + description: t("otpEmailSentDescription") }); return; } @@ -202,7 +203,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { .catch((e) => { console.error(e); setWhitelistError( - formatAxiosError(e, t('otpEmailErrorAuthenticate')) + formatAxiosError(e, t("otpEmailErrorAuthenticate")) ); }) .then(() => setLoadingLogin(false)); @@ -227,7 +228,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { .catch((e) => { console.error(e); setPincodeError( - formatAxiosError(e, t('pincodeErrorAuthenticate')) + formatAxiosError(e, t("pincodeErrorAuthenticate")) ); }) .then(() => setLoadingLogin(false)); @@ -255,7 +256,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { .catch((e) => { console.error(e); setPasswordError( - formatAxiosError(e, t('passwordErrorAuthenticate')) + formatAxiosError(e, t("passwordErrorAuthenticate")) ); }) .finally(() => setLoadingLogin(false)); @@ -276,30 +277,25 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { } } + function getTitle() { + return t("authenticationRequired"); + } + + function getSubtitle(resourceName: string) { + return numMethods > 1 + ? t("authenticationMethodChoose", { name: props.resource.name }) + : t("authenticationRequest", { name: props.resource.name }); + } + return (
{!accessDenied ? (
-
- - {t('poweredBy')}{" "} - - Pangolin - - -
- {t('authenticationRequired')} + {getTitle()} - {numMethods > 1 - ? t('authenticationMethodChoose', {name: props.resource.name}) - : t('authenticationRequest', {name: props.resource.name})} + {getSubtitle(props.resource.name)} @@ -329,19 +325,19 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { {props.methods.password && ( {" "} - {t('password')} + {t("password")} )} {props.methods.sso && ( {" "} - {t('user')} + {t("user")} )} {props.methods.whitelist && ( {" "} - {t('email')} + {t("email")} )} @@ -364,7 +360,9 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { render={({ field }) => ( - {t('pincodeInput')} + {t( + "pincodeInput" + )}
@@ -433,7 +431,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { disabled={loadingLogin} > - {t('pincodeSubmit')} + {t("pincodeSubmit")} @@ -459,7 +457,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { render={({ field }) => ( - {t('password')} + {t("password")} - {t('passwordSubmit')} + {t("passwordSubmit")} @@ -528,7 +526,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { render={({ field }) => ( - {t('email')} + {t("email")} - {t('otpEmailDescription')} + {t( + "otpEmailDescription" + )} @@ -559,7 +559,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { disabled={loadingLogin} > - {t('otpEmailSend')} + {t("otpEmailSend")} @@ -581,7 +581,9 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { render={({ field }) => ( - {t('otpEmail')} + {t( + "otpEmail" + )} - {t('otpEmailSubmit')} + {t("otpEmailSubmit")} @@ -634,7 +636,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) { {supporterStatus?.visible && (
- {t('noSupportKey')} + {t("noSupportKey")}
)} diff --git a/src/app/auth/signup/SignupForm.tsx b/src/app/auth/signup/SignupForm.tsx index bd693180..27f4921c 100644 --- a/src/app/auth/signup/SignupForm.tsx +++ b/src/app/auth/signup/SignupForm.tsx @@ -57,7 +57,9 @@ export default function SignupForm({ }: SignupFormProps) { const router = useRouter(); - const api = createApiClient(useEnvContext()); + const { env } = useEnvContext(); + + const api = createApiClient({ env }); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -116,8 +118,8 @@ export default function SignupForm({ } return ( - - + +
- +
{t('password')} - + @@ -177,10 +176,7 @@ export default function SignupForm({ {t('confirmPassword')} - + diff --git a/src/app/globals.css b/src/app/globals.css index d9544f44..e643cfb6 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -111,6 +111,9 @@ --radius-lg: var(--radius); --radius-md: calc(var(--radius) - 2px); --radius-sm: calc(var(--radius) - 4px); + + --shadow-2xs: 0 1px 1px rgba(0, 0, 0, 0.03); + --inset-shadow-2xs: inset 0 1px 1px rgba(0, 0, 1, 0.03); } @layer base { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f022367c..1ad8d10a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,6 @@ import type { Metadata } from "next"; import "./globals.css"; import { Inter } from "next/font/google"; -import { Toaster } from "@/components/ui/toaster"; import { ThemeProvider } from "@app/providers/ThemeProvider"; import EnvProvider from "@app/providers/EnvProvider"; import { pullEnv } from "@app/lib/pullEnv"; @@ -15,10 +14,11 @@ import LicenseViolation from "@app/components/LicenseViolation"; import { cache } from "react"; import { NextIntlClientProvider } from "next-intl"; import { getLocale } from "next-intl/server"; +import { Toaster } from "@app/components/ui/toaster"; export const metadata: Metadata = { title: `Dashboard - Pangolin`, - description: "" + description: "", }; export const dynamic = "force-dynamic"; @@ -83,4 +83,4 @@ export default async function RootLayout({ ); -} +} \ No newline at end of file diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index 72b16044..546dba26 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -10,7 +10,8 @@ import { Workflow, KeyRound, TicketCheck, - User + User, + Globe } from "lucide-react"; export type SidebarNavSection = { @@ -31,6 +32,11 @@ export const orgNavSections: SidebarNavSection[] = [ title: "sidebarResources", href: "/{orgId}/settings/resources", icon: + }, + { + title: "sidebarDomains", + href: "/{orgId}/settings/domains", + icon: } ] }, diff --git a/src/app/page.tsx b/src/app/page.tsx index 2cc0b4fb..91ca5686 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -75,35 +75,33 @@ export default async function Page(props: { const allCookies = await cookies(); const lastOrgCookie = allCookies.get("pangolin-last-org")?.value; - if (lastOrgCookie && orgs.length > 0) { - const lastOrgExists = orgs.some((org) => org.orgId === lastOrgCookie); - if (lastOrgExists) { - redirect(`/${lastOrgCookie}`); + const lastOrgExists = orgs.some((org) => org.orgId === lastOrgCookie); + if (lastOrgExists) { + redirect(`/${lastOrgCookie}`); + } else { + const ownedOrg = orgs.find((org) => org.isOwner); + if (ownedOrg) { + redirect(`/${ownedOrg.orgId}`); } else { - const ownedOrg = orgs.find((org) => org.isOwner); - if (ownedOrg) { - redirect(`/${ownedOrg.orgId}`); - } else { - redirect("/setup"); - } + redirect("/setup"); } } - return ( - - -
- ({ - name: org.name, - id: org.orgId - }))} - /> -
-
-
- ); + // return ( + // + // + //
+ // ({ + // name: org.name, + // id: org.orgId + // }))} + // /> + //
+ //
+ //
+ // ); } diff --git a/src/components/DomainPicker.tsx b/src/components/DomainPicker.tsx new file mode 100644 index 00000000..2f33b659 --- /dev/null +++ b/src/components/DomainPicker.tsx @@ -0,0 +1,499 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + AlertCircle, + CheckCircle2, + Building2, + Zap, + ArrowUpDown +} from "lucide-react"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { createApiClient, formatAxiosError } from "@/lib/api"; +import { useEnvContext } from "@/hooks/useEnvContext"; +import { toast } from "@/hooks/useToast"; +import { ListDomainsResponse } from "@server/routers/domain/listDomains"; +import { AxiosResponse } from "axios"; +import { cn } from "@/lib/cn"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useTranslations } from "next-intl"; + +type OrganizationDomain = { + domainId: string; + baseDomain: string; + verified: boolean; + type: "ns" | "cname"; +}; + +type AvailableOption = { + domainNamespaceId: string; + fullDomain: string; + domainId: string; +}; + +type DomainOption = { + id: string; + domain: string; + type: "organization" | "provided"; + verified?: boolean; + domainType?: "ns" | "cname"; + domainId?: string; + domainNamespaceId?: string; + subdomain?: string; +}; + +interface DomainPickerProps { + orgId: string; + onDomainChange?: (domainInfo: { + domainId: string; + domainNamespaceId?: string; + type: "organization" | "provided"; + subdomain?: string; + fullDomain: string; + baseDomain: string; + }) => void; +} + +export default function DomainPicker({ + orgId, + onDomainChange +}: DomainPickerProps) { + const { env } = useEnvContext(); + const api = createApiClient({ env }); + const t = useTranslations(); + + const [userInput, setUserInput] = useState(""); + const [selectedOption, setSelectedOption] = useState( + null + ); + const [availableOptions, setAvailableOptions] = useState( + [] + ); + const [isChecking, setIsChecking] = useState(false); + const [organizationDomains, setOrganizationDomains] = useState< + OrganizationDomain[] + >([]); + const [loadingDomains, setLoadingDomains] = useState(false); + const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); + const [activeTab, setActiveTab] = useState< + "all" | "organization" | "provided" + >("all"); + const [providedDomainsShown, setProvidedDomainsShown] = useState(3); + + useEffect(() => { + const loadOrganizationDomains = async () => { + setLoadingDomains(true); + try { + const response = await api.get< + AxiosResponse + >(`/org/${orgId}/domains`); + if (response.status === 200) { + const domains = response.data.data.domains + .filter( + (domain) => + domain.type === "ns" || domain.type === "cname" + ) + .map((domain) => ({ + ...domain, + type: domain.type as "ns" | "cname" + })); + setOrganizationDomains(domains); + } + } catch (error) { + console.error("Failed to load organization domains:", error); + toast({ + variant: "destructive", + title: "Error", + description: "Failed to load organization domains" + }); + } finally { + setLoadingDomains(false); + } + }; + + loadOrganizationDomains(); + }, [orgId, api]); + + // Generate domain options based on user input + const generateDomainOptions = (): DomainOption[] => { + const options: DomainOption[] = []; + + if (!userInput.trim()) return options; + + // Check if input is more than one level deep (contains multiple dots) + const isMultiLevel = (userInput.match(/\./g) || []).length > 1; + + // Add organization domain options + organizationDomains.forEach((orgDomain) => { + if (orgDomain.type === "cname") { + // For CNAME domains, check if the user input matches exactly + if ( + orgDomain.baseDomain.toLowerCase() === + userInput.toLowerCase() + ) { + options.push({ + id: `org-${orgDomain.domainId}`, + domain: orgDomain.baseDomain, + type: "organization", + verified: orgDomain.verified, + domainType: "cname", + domainId: orgDomain.domainId + }); + } + } else if (orgDomain.type === "ns") { + // For NS domains, check if the user input could be a subdomain + const userInputLower = userInput.toLowerCase(); + const baseDomainLower = orgDomain.baseDomain.toLowerCase(); + + // Check if user input ends with the base domain + if (userInputLower.endsWith(`.${baseDomainLower}`)) { + const subdomain = userInputLower.slice( + 0, + -(baseDomainLower.length + 1) + ); + options.push({ + id: `org-${orgDomain.domainId}`, + domain: userInput, + type: "organization", + verified: orgDomain.verified, + domainType: "ns", + domainId: orgDomain.domainId, + subdomain: subdomain + }); + } else if (userInputLower === baseDomainLower) { + // Exact match for base domain + options.push({ + id: `org-${orgDomain.domainId}`, + domain: orgDomain.baseDomain, + type: "organization", + verified: orgDomain.verified, + domainType: "ns", + domainId: orgDomain.domainId + }); + } + } + }); + + // Add provided domain options (always try to match provided domains) + availableOptions.forEach((option) => { + options.push({ + id: `provided-${option.domainNamespaceId}`, + domain: option.fullDomain, + type: "provided", + domainNamespaceId: option.domainNamespaceId, + domainId: option.domainId, + }); + }); + + // Sort options + return options.sort((a, b) => { + const comparison = a.domain.localeCompare(b.domain); + return sortOrder === "asc" ? comparison : -comparison; + }); + }; + + const domainOptions = generateDomainOptions(); + + // Filter options based on active tab + const filteredOptions = domainOptions.filter((option) => { + if (activeTab === "all") return true; + return option.type === activeTab; + }); + + // Separate organization and provided options for pagination + const organizationOptions = filteredOptions.filter( + (opt) => opt.type === "organization" + ); + const allProvidedOptions = filteredOptions.filter( + (opt) => opt.type === "provided" + ); + const providedOptions = allProvidedOptions.slice(0, providedDomainsShown); + const hasMoreProvided = allProvidedOptions.length > providedDomainsShown; + + // Handle option selection + const handleOptionSelect = (option: DomainOption) => { + setSelectedOption(option); + + if (option.type === "organization") { + if (option.domainType === "cname") { + onDomainChange?.({ + domainId: option.domainId!, + type: "organization", + subdomain: undefined, + fullDomain: option.domain, + baseDomain: option.domain + }); + } else if (option.domainType === "ns") { + const subdomain = option.subdomain || ""; + onDomainChange?.({ + domainId: option.domainId!, + type: "organization", + subdomain: subdomain || undefined, + fullDomain: option.domain, + baseDomain: option.domain + }); + } + } else if (option.type === "provided") { + // Extract subdomain from full domain + const parts = option.domain.split("."); + const subdomain = parts[0]; + const baseDomain = parts.slice(1).join("."); + onDomainChange?.({ + domainId: option.domainId!, + domainNamespaceId: option.domainNamespaceId, + type: "provided", + subdomain: subdomain, + fullDomain: option.domain, + baseDomain: baseDomain + }); + } + }; + + return ( +
+ {/* Domain Input */} +
+ + { + // Only allow letters, numbers, hyphens, and periods + const validInput = e.target.value.replace( + /[^a-zA-Z0-9.-]/g, + "" + ); + setUserInput(validInput); + }} + /> +

+ {t("domainPickerDescription")} +

+
+ + {/* Tabs and Sort Toggle */} +
+ + setActiveTab( + value as "all" | "organization" | "provided" + ) + } + > + + + {t("domainPickerTabAll")} + + + {t("domainPickerTabOrganization")} + + + {t("domainPickerTabProvided")} + + + + +
+ + {/* Loading State */} + {isChecking && ( +
+
+
+ {t("domainPickerCheckingAvailability")} +
+
+ )} + + {/* No Options */} + {!isChecking && + filteredOptions.length === 0 && + userInput.trim() && ( + + + + {t("domainPickerNoMatchingDomains", { userInput })} + + + )} + + {/* Domain Options */} + {!isChecking && filteredOptions.length > 0 && ( +
+ {/* Organization Domains */} + {organizationOptions.length > 0 && ( +
+
+ +

+ {t("domainPickerOrganizationDomains")} +

+
+
+ {organizationOptions.map((option) => ( +
+ option.verified && handleOptionSelect(option) + } + > +
+
+
+

+ {option.domain} +

+ {/* */} + {/* {option.domainType} */} + {/* */} + {option.verified ? ( + + ) : ( + + )} +
+ {option.subdomain && ( +

+ {t( + "domainPickerSubdomain", + { + subdomain: + option.subdomain + } + )} +

+ )} + {!option.verified && ( +

+ Domain is unverified +

+ )} +
+ {selectedOption?.id === + option.id && ( + + )} +
+
+ ))} +
+
+ )} + + {/* Provided Domains */} + {providedOptions.length > 0 && ( +
+
+ +
+ {t("domainPickerProvidedDomains")} +
+
+
+ {providedOptions.map((option) => ( +
+ handleOptionSelect(option) + } + > +
+
+

+ {option.domain} +

+

+ {t( + "domainPickerNamespace", + { + namespace: + option.domainNamespaceId as string + } + )} +

+
+ {selectedOption?.id === + option.id && ( + + )} +
+
+ ))} +
+ {hasMoreProvided && ( + + )} +
+ )} +
+ )} +
+ ); +} + +function debounce any>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeout: NodeJS.Timeout | null = null; + + return (...args: Parameters) => { + if (timeout) clearTimeout(timeout); + + timeout = setTimeout(() => { + func(...args); + }, wait); + }; +} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 95984a5e..7d99a773 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -30,8 +30,9 @@ export async function Layout({ }: LayoutProps) { const allCookies = await cookies(); const sidebarStateCookie = allCookies.get("pangolin-sidebar-state")?.value; - - const initialSidebarCollapsed = sidebarStateCookie === "collapsed" || + + const initialSidebarCollapsed = + sidebarStateCookie === "collapsed" || (sidebarStateCookie !== "expanded" && defaultSidebarCollapsed); return ( @@ -49,7 +50,7 @@ export async function Layout({ {/* Main content area */}
@@ -69,7 +70,10 @@ export async function Layout({ {/* Main content */}
-
+
{children}
diff --git a/src/components/LayoutHeader.tsx b/src/components/LayoutHeader.tsx index e3fd61ac..5c391cba 100644 --- a/src/components/LayoutHeader.tsx +++ b/src/components/LayoutHeader.tsx @@ -7,6 +7,8 @@ import Link from "next/link"; import ProfileIcon from "@app/components/ProfileIcon"; import ThemeSwitcher from "@app/components/ThemeSwitcher"; import { useTheme } from "next-themes"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { Badge } from "./ui/badge"; interface LayoutHeaderProps { showTopBar: boolean; @@ -15,6 +17,7 @@ interface LayoutHeaderProps { export function LayoutHeader({ showTopBar }: LayoutHeaderProps) { const { theme } = useTheme(); const [path, setPath] = useState(""); + const { env } = useEnvContext(); useEffect(() => { function getPath() { @@ -56,7 +59,6 @@ export function LayoutHeader({ showTopBar }: LayoutHeaderProps) {
- {/* Profile controls on the right */} {showTopBar && (
diff --git a/src/components/LayoutSidebar.tsx b/src/components/LayoutSidebar.tsx index 93100597..98ef87eb 100644 --- a/src/components/LayoutSidebar.tsx +++ b/src/components/LayoutSidebar.tsx @@ -152,7 +152,7 @@ export function LayoutSidebar({ onClick={() => setIsSidebarCollapsed(!isSidebarCollapsed) } - className="cursor-pointer absolute -right-2.5 top-1/2 transform -translate-y-1/2 w-2 h-8 rounded-full flex items-center justify-center transition-all duration-200 ease-in-out hover:scale-110 group" + className="cursor-pointer absolute -right-2.5 top-1/2 transform -translate-y-1/2 w-2 h-8 rounded-full flex items-center justify-center transition-all duration-200 ease-in-out hover:scale-110 group z-[60]" aria-label={ isSidebarCollapsed ? "Expand sidebar" diff --git a/src/components/OrgSelector.tsx b/src/components/OrgSelector.tsx index 9040c613..f8f5446e 100644 --- a/src/components/OrgSelector.tsx +++ b/src/components/OrgSelector.tsx @@ -80,8 +80,8 @@ export function OrgSelector({ orgId, orgs, isCollapsed = false }: OrgSelectorPro - diff --git a/src/components/OrganizationLanding.tsx b/src/components/OrganizationLanding.tsx index a443fcf3..2f3125b0 100644 --- a/src/components/OrganizationLanding.tsx +++ b/src/components/OrganizationLanding.tsx @@ -11,6 +11,7 @@ import { import { Button } from "@/components/ui/button"; import Link from "next/link"; import { ArrowRight, Plus } from "lucide-react"; +import { useEnvContext } from "@app/hooks/useEnvContext"; import { useTranslations } from "next-intl"; interface Organization { @@ -29,6 +30,8 @@ export default function OrganizationLanding({ }: OrganizationLandingProps) { const [selectedOrg, setSelectedOrg] = useState(null); + const { env } = useEnvContext(); + const handleOrgClick = (orgId: string) => { setSelectedOrg(orgId); }; diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 410d3093..796a4bc8 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -19,7 +19,7 @@ export function SettingsSectionForm({ }: { children: React.ReactNode; }) { - return
{children}
; + return
{children}
; } export function SettingsSectionTitle({ diff --git a/src/components/SidebarNav.tsx b/src/components/SidebarNav.tsx index 530eb726..7e8ad336 100644 --- a/src/components/SidebarNav.tsx +++ b/src/components/SidebarNav.tsx @@ -48,6 +48,7 @@ export function SidebarNav({ const niceId = params.niceId as string; const resourceId = params.resourceId as string; const userId = params.userId as string; + const apiKeyId = params.apiKeyId as string; const clientId = params.clientId as string; const { licenseStatus, isUnlocked } = useLicenseStatusContext(); const { user } = useUserContext(); @@ -59,6 +60,7 @@ export function SidebarNav({ .replace("{niceId}", niceId) .replace("{resourceId}", resourceId) .replace("{userId}", userId) + .replace("{apiKeyId}", apiKeyId) .replace("{clientId}", clientId); } diff --git a/src/components/SupporterStatus.tsx b/src/components/SupporterStatus.tsx index 7440b1ce..74d4bf8b 100644 --- a/src/components/SupporterStatus.tsx +++ b/src/components/SupporterStatus.tsx @@ -67,7 +67,8 @@ export default function SupporterStatus({ isCollapsed = false }: SupporterStatus const [keyOpen, setKeyOpen] = useState(false); const [purchaseOptionsOpen, setPurchaseOptionsOpen] = useState(false); - const api = createApiClient(useEnvContext()); + const { env } = useEnvContext(); + const api = createApiClient({ env }); const t = useTranslations(); const formSchema = z.object({ diff --git a/src/components/tags/tag-input.tsx b/src/components/tags/tag-input.tsx index 1d5f0997..789a127c 100644 --- a/src/components/tags/tag-input.tsx +++ b/src/components/tags/tag-input.tsx @@ -497,7 +497,7 @@ const TagInput = React.forwardRef(
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index 8168a85a..3bcf2bea 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -16,9 +16,9 @@ const badgeVariants = cva( destructive: "border-transparent bg-destructive text-destructive-foreground", outline: "text-foreground", - green: "border-transparent bg-green-500", - yellow: "border-transparent bg-yellow-500", - red: "border-transparent bg-red-300", + green: "border-green-600 bg-green-500/20 text-green-700 dark:text-green-300", + yellow: "border-yellow-600 bg-yellow-500/20 text-yellow-700 dark:text-yellow-300", + red: "border-red-400 bg-red-300/20 text-red-600 dark:text-red-300", }, }, defaultVariants: { diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 8f10ac19..d77bc39a 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -71,7 +71,7 @@ const Button = React.forwardRef( disabled={loading || props.disabled} // Disable button when loading {...props} > - {/* {loading && } */} + {loading && } {props.children} ); diff --git a/src/components/ui/data-table.tsx b/src/components/ui/data-table.tsx index 4dfe6f49..75a37e58 100644 --- a/src/components/ui/data-table.tsx +++ b/src/components/ui/data-table.tsx @@ -107,8 +107,8 @@ export function DataTable({
{onRefresh && ( -
); -} \ No newline at end of file +} diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx index e101715d..b24a2ba3 100644 --- a/src/components/ui/progress.tsx +++ b/src/components/ui/progress.tsx @@ -3,24 +3,60 @@ import * as React from "react"; import * as ProgressPrimitive from "@radix-ui/react-progress"; import { cn } from "@app/lib/cn"; +import { cva, type VariantProps } from "class-variance-authority"; + +const progressVariants = cva( + "border relative h-2 w-full overflow-hidden rounded-full", + { + variants: { + variant: { + default: "bg-muted", + success: "bg-muted", + warning: "bg-muted", + danger: "bg-muted" + } + }, + defaultVariants: { + variant: "default" + } + } +); + +const indicatorVariants = cva( + "h-full w-full flex-1 transition-all", + { + variants: { + variant: { + default: "bg-primary", + success: "bg-green-500", + warning: "bg-yellow-500", + danger: "bg-red-500" + } + }, + defaultVariants: { + variant: "default" + } + } +); + +type ProgressProps = React.ComponentProps & + VariantProps; function Progress({ className, value, + variant, ...props -}: React.ComponentProps) { +}: ProgressProps) { return ( diff --git a/src/lib/pullEnv.ts b/src/lib/pullEnv.ts index 71dcbe68..3b3f43e8 100644 --- a/src/lib/pullEnv.ts +++ b/src/lib/pullEnv.ts @@ -47,7 +47,9 @@ export function pullEnv(): Env { ? true : false, enableClients: - process.env.FLAGS_ENABLE_CLIENTS === "true" ? true : false - } + process.env.FLAGS_ENABLE_CLIENTS === "true" ? true : false, + hideSupporterKey: + process.env.HIDE_SUPPORTER_KEY === "true" ? true : false + }, }; } diff --git a/src/lib/types/env.ts b/src/lib/types/env.ts index ab2dd66c..2bd36505 100644 --- a/src/lib/types/env.ts +++ b/src/lib/types/env.ts @@ -25,5 +25,6 @@ export type Env = { disableLocalSites: boolean; disableBasicWireguardSites: boolean; enableClients: boolean; - }; + hideSupporterKey: boolean; + }, };