diff --git a/README.md b/README.md index bf5dc19..01af302 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,7 @@ Uninstalling preserves the database and environment by default. Pass `--purge-da The node database is a local embedded Turso/SQLite file. Media remains in storage controlled by each user, so exported accounts retain portable media URLs and can move between Synapsis nodes without requiring the old node to transfer a shared upload directory. -Stuffbox is the default integration. New installs use `https://stuffbox.xyz`; set `STUFFBOX_URL` to a different public URL when using another or self-hosted Stuffbox service. Each Synapsis install registers its callback automatically the first time a user connects, so node operators do not need to create or configure a Stuffbox client ID. Synapsis uses a consent and PKCE flow, keeps the resulting tokens encrypted with `AUTH_SECRET`, and sends file bytes directly from the user's browser to Stuffbox. - -User-owned S3-compatible storage remains available as an advanced fallback. Supported providers include AWS S3, Cloudflare R2, Backblaze B2, Wasabi, and Contabo. +Stuffbox is the exclusive media-storage integration. New installs use `https://stuffbox.xyz`; set `STUFFBOX_URL` to a different public URL when using another or self-hosted Stuffbox service. Each Synapsis install registers its callback automatically the first time a user connects, so node operators do not need to create or configure a Stuffbox client ID. Synapsis uses a consent and PKCE flow, keeps the resulting tokens encrypted with `AUTH_SECRET`, and sends file bytes directly from the user's browser to Stuffbox. ## Encrypted direct messages @@ -99,7 +97,7 @@ npm run build - **Framework:** Next.js 16 and React 19 - **Database:** embedded Turso with Drizzle ORM's relational-query v2 API - **Identity:** DIDs and per-user signing keys -- **Media:** user-owned Stuffbox or S3-compatible storage +- **Media:** user-owned Stuffbox storage - **Federation:** Synapsis Swarm discovery and signed interactions - **Deployment:** native Node.js process managed by systemd diff --git a/package-lock.json b/package-lock.json index ebd9570..23b45c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-s3": "^3.972.0", "@tursodatabase/database": "0.7.0", "@upstash/redis": "^1.34.3", "bcryptjs": "^2.4.3", @@ -88,904 +87,6 @@ } } }, - "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.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.972.0.tgz", - "integrity": "sha512-ghpDQtjZvbhbnHWymq/V5TL8NppdAGF2THAxYRRBLCJ5JRlq71T24NdovAzvzYaGdH7HtcRkgErBRsFT1gtq4g==", - "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.972.0", - "@aws-sdk/credential-provider-node": "3.972.0", - "@aws-sdk/middleware-bucket-endpoint": "3.972.0", - "@aws-sdk/middleware-expect-continue": "3.972.0", - "@aws-sdk/middleware-flexible-checksums": "3.972.0", - "@aws-sdk/middleware-host-header": "3.972.0", - "@aws-sdk/middleware-location-constraint": "3.972.0", - "@aws-sdk/middleware-logger": "3.972.0", - "@aws-sdk/middleware-recursion-detection": "3.972.0", - "@aws-sdk/middleware-sdk-s3": "3.972.0", - "@aws-sdk/middleware-ssec": "3.972.0", - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/region-config-resolver": "3.972.0", - "@aws-sdk/signature-v4-multi-region": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "3.972.0", - "@aws-sdk/util-user-agent-node": "3.972.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/eventstream-serde-browser": "^4.2.8", - "@smithy/eventstream-serde-config-resolver": "^4.3.8", - "@smithy/eventstream-serde-node": "^4.2.8", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-blob-browser": "^4.2.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/hash-stream-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/md5-js": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.972.0.tgz", - "integrity": "sha512-5qw6qLiRE4SUiz0hWy878dSR13tSVhbTWhsvFT8mGHe37NRRiaobm5MA2sWD0deRAuO98djSiV+dhWXa1xIFNw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/middleware-host-header": "3.972.0", - "@aws-sdk/middleware-logger": "3.972.0", - "@aws-sdk/middleware-recursion-detection": "3.972.0", - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/region-config-resolver": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "3.972.0", - "@aws-sdk/util-user-agent-node": "3.972.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz", - "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@aws-sdk/xml-builder": "3.972.0", - "@smithy/core": "^3.20.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/crc64-nvme": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.0.tgz", - "integrity": "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.0.tgz", - "integrity": "sha512-kKHoNv+maHlPQOAhYamhap0PObd16SAb3jwaY0KYgNTiSbeXlbGUZPLioo9oA3wU10zItJzx83ClU7d7h40luA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.0.tgz", - "integrity": "sha512-xzEi81L7I5jGUbpmqEHCe7zZr54hCABdj4H+3LzktHYuovV/oqnvoDdvZpGFR0e/KAw1+PL38NbGrpG30j6qlA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.0.tgz", - "integrity": "sha512-ruhAMceUIq2aknFd3jhWxmO0P0Efab5efjyIXOkI9i80g+zDY5VekeSxfqRKStEEJSKSCHDLQuOu0BnAn4Rzew==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/credential-provider-env": "3.972.0", - "@aws-sdk/credential-provider-http": "3.972.0", - "@aws-sdk/credential-provider-login": "3.972.0", - "@aws-sdk/credential-provider-process": "3.972.0", - "@aws-sdk/credential-provider-sso": "3.972.0", - "@aws-sdk/credential-provider-web-identity": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.0.tgz", - "integrity": "sha512-SsrsFJsEYAJHO4N/r2P0aK6o8si6f1lprR+Ej8J731XJqTckSGs/HFHcbxOyW/iKt+LNUvZa59/VlJmjhF4bEQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.0.tgz", - "integrity": "sha512-wwJDpEGl6+sOygic8QKu0OHVB8SiodqF1fr5jvUlSFfS6tJss/E9vBc2aFjl7zI6KpAIYfIzIgM006lRrZtWCQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.972.0", - "@aws-sdk/credential-provider-http": "3.972.0", - "@aws-sdk/credential-provider-ini": "3.972.0", - "@aws-sdk/credential-provider-process": "3.972.0", - "@aws-sdk/credential-provider-sso": "3.972.0", - "@aws-sdk/credential-provider-web-identity": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.0.tgz", - "integrity": "sha512-nmzYhamLDJ8K+v3zWck79IaKMc350xZnWsf/GeaXO6E3MewSzd3lYkTiMi7lEp3/UwDm9NHfPguoPm+mhlSWQQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.0.tgz", - "integrity": "sha512-6mYyfk1SrMZ15cH9T53yAF4YSnvq4yU1Xlgm3nqV1gZVQzmF5kr4t/F3BU3ygbvzi4uSwWxG3I3TYYS5eMlAyg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.972.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/token-providers": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.0.tgz", - "integrity": "sha512-vsJXBGL8H54kz4T6do3p5elATj5d1izVGUXMluRJntm9/I0be/zUYtdd4oDTM2kSUmd4Zhyw3fMQ9lw7CVhd4A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.0.tgz", - "integrity": "sha512-IrIjAehc3PrseAGfk2ldtAf+N0BAnNHR1DCZIDh9IAcFrTVWC3Fi9KJdtabrxcY3Onpt/8opOco4EIEAWgMz7A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-arn-parser": "3.972.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.0.tgz", - "integrity": "sha512-xyhDoY0qse8MvQC4RZCpT5WoIQ4/kwqv71Dh1s3mdXjL789Z4a6L/khBTSXECR5+egSZ960AInj3aR+CrezDRQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.0.tgz", - "integrity": "sha512-zxK0ezmT7fLEPJ650S8QBc4rGDq5+5rdsLnnuZ6hPaZE4/+QtUoTw+gSDETyiWodNcRuz2ZWnqi17K+7nKtSRg==", - "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.972.0", - "@aws-sdk/crc64-nvme": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.0.tgz", - "integrity": "sha512-3eztFI6F9/eHtkIaWKN3nT+PM+eQ6p1MALDuNshFk323ixuCZzOOVT8oUqtZa30Z6dycNXJwhlIq7NhUVFfimw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.0.tgz", - "integrity": "sha512-WpsxoVPzbGPQGb/jupNYjpE0REcCPtjz7Q7zAt+dyo7fxsLBn4J+Rp6AYzSa04J9VrmrvCqCbVLu6B88PlSKSQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.0.tgz", - "integrity": "sha512-ZvdyVRwzK+ra31v1pQrgbqR/KsLD+wwJjHgko6JfoKUBIcEfAwJzQKO6HspHxdHWTVUz6MgvwskheR/TTYZl2g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.0.tgz", - "integrity": "sha512-F2SmUeO+S6l1h6dydNet3BQIk173uAkcfU1HDkw/bUdRLAnh15D3HP9vCZ7oCPBNcdEICbXYDmx0BR9rRUHGlQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.0.tgz", - "integrity": "sha512-0bcKFXWx+NZ7tIlOo7KjQ+O2rydiHdIQahrq+fN6k9Osky29v17guy68urUKfhTobR6iY6KvxkroFWaFtTgS5w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-arn-parser": "3.972.0", - "@smithy/core": "^3.20.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.0.tgz", - "integrity": "sha512-cEr2HtK4R2fi8Y0P95cjbr4KJOjKBt8ms95mEJhabJN8KM4CpD4iS/J1lhvMj+qWir0KBTV6gKmxECXdfL9S6w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.0.tgz", - "integrity": "sha512-kFHQm2OCBJCzGWRafgdWHGFjitUXY/OxXngymcX4l8CiyiNDZB27HDDBg2yLj3OUJc4z4fexLMmP8r9vgag19g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.20.6", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.972.0.tgz", - "integrity": "sha512-QGlbnuGzSQJVG6bR9Qw6G0Blh6abFR4VxNa61ttMbzy9jt28xmk2iGtrYLrQPlCCPhY6enHqjTWm3n3LOb0wAw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.972.0", - "@aws-sdk/middleware-host-header": "3.972.0", - "@aws-sdk/middleware-logger": "3.972.0", - "@aws-sdk/middleware-recursion-detection": "3.972.0", - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/region-config-resolver": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "3.972.0", - "@aws-sdk/util-user-agent-node": "3.972.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.0.tgz", - "integrity": "sha512-JyOf+R/6vJW8OEVFCAyzEOn2reri/Q+L0z9zx4JQSKWvTmJ1qeFO25sOm8VIfB8URKhfGRTQF30pfYaH2zxt/A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.972.0.tgz", - "integrity": "sha512-2udiRijmjpN81Pvajje4TsjbXDZNP6K9bYUanBYH8hXa/tZG5qfGCySD+TyX0sgDxCQmEDMg3LaQdfjNHBDEgQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.972.0.tgz", - "integrity": "sha512-kWlXG+y5nZhgXGEtb72Je+EvqepBPs8E3vZse//1PYLWs2speFqbGE/ywCXmzEJgHgVqSB/u/lqBvs5WlYmSqQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/nested-clients": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.0.tgz", - "integrity": "sha512-RM5Mmo/KJ593iMSrALlHEOcc9YOIyOsDmS5x2NLOMdEmzv1o00fcpAkCQ02IGu1eFneBFT7uX0Mpag0HI+Cz2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.3.tgz", - "integrity": "sha512-FNUqAjlKAGA7GM05kywE99q8wiPHPZqrzhq3wXRga6PRD6A0kzT85Pb0AzYBVTBRpSrKyyr6M92Y6bnSBVp2BA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.0.tgz", - "integrity": "sha512-eOLdkQyoRbDgioTS3Orr7iVsVEutJyMZxvyZ6WAF95IrF0kfWx5Rd/KXnfbnG/VKa2CvjZiitWfouLzfVEyvJA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.0.tgz", - "integrity": "sha512-GOy+AiSrE9kGiojiwlZvVVSXwylu4+fmP0MJfvras/MwP09RB/YtQuOVR1E0fKQc6OMwaTNBjgAbOEhxuWFbAw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz", - "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", - "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", @@ -3004,738 +2105,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", - "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/chunked-blob-reader": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", - "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/chunked-blob-reader-native": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", - "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", - "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.21.0.tgz", - "integrity": "sha512-bg2TfzgsERyETAxc/Ims/eJX8eAnIeTi4r4LHpMpfF/2NyO6RsWis0rjKcCPaGksljmOb23BZRiCeT/3NvwkXw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-serde": "^4.2.9", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", - "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-codec": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz", - "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.12.0", - "@smithy/util-hex-encoding": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz", - "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz", - "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz", - "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz", - "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-codec": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", - "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-blob-browser": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.9.tgz", - "integrity": "sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/chunked-blob-reader": "^5.2.0", - "@smithy/chunked-blob-reader-native": "^4.2.1", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", - "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-stream-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.8.tgz", - "integrity": "sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", - "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/md5-js": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.8.tgz", - "integrity": "sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", - "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.10.tgz", - "integrity": "sha512-kwWpNltpxrvPabnjEFvwSmA+66l6s2ReCvgVSzW/z92LU4T28fTdgZ18IdYRYOrisu2NMQ0jUndRScbO65A/zg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.21.0", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-middleware": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "4.4.26", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.26.tgz", - "integrity": "sha512-ozZMoTAr+B2aVYfLYfkssFvc8ZV3p/vLpVQ7/k277xxUOA9ykSPe5obL2j6yHfbdrM/SZV7qj0uk/hSqavHrLw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", - "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", - "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", - "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz", - "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", - "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", - "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", - "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-uri-escape": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", - "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", - "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", - "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", - "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "4.10.11", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.11.tgz", - "integrity": "sha512-6o804SCyHGMXAb5mFJ+iTy9kVKv7F91a9szN0J+9X6p8A0NrdpUxdaC57aye2ipQkP2C4IAqETEpGZ0Zj77Haw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.21.0", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", - "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", - "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.25.tgz", - "integrity": "sha512-8ugoNMtss2dJHsXnqsibGPqoaafvWJPACmYKxJ4E6QWaDrixsAemmiMMAVbvwYadjR0H9G2+AlzsInSzRi8PSw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.28", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.28.tgz", - "integrity": "sha512-mjUdcP8h3E0K/XvNMi9oBXRV3DMCzeRiYIieZ1LQ7jq5tu6GH/GTWym7a1xIIE0pKSoLcpGsaImuQhGPSIJzAA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.4.6", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-endpoints": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", - "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", - "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-retry": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", - "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-stream": { - "version": "4.5.10", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz", - "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-waiter": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz", - "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/uuid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", - "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -3975,6 +2344,66 @@ "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.7.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.7.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", @@ -5244,12 +3673,6 @@ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", "license": "MIT" }, - "node_modules/bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -6710,24 +5133,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -9581,18 +7986,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", - "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", - "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", diff --git a/package.json b/package.json index 767a5ac..b96a835 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "test:watch": "vitest" }, "dependencies": { - "@aws-sdk/client-s3": "^3.972.0", "@tursodatabase/database": "0.7.0", "@upstash/redis": "^1.34.3", "bcryptjs": "^2.4.3", diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 1f57614..98be888 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -3,11 +3,9 @@ import { useEffect, useRef, useState } from 'react'; import Link from 'next/link'; import AutoTextarea from '@/components/AutoTextarea'; -import { StorageSessionPrompt } from '@/components/StorageSessionPrompt'; import { StorageConfigurationPrompt } from '@/components/StorageConfigurationPrompt'; import { useToast } from '@/lib/contexts/ToastContext'; import { useAccentColor } from '@/lib/contexts/AccentColorContext'; -import { refreshStorageSession } from '@/lib/storage/client'; import { getStorageProvider, MediaUploadError, uploadMediaFile } from '@/lib/stuffbox/browser-upload'; import { hasUnsavedChanges } from '@/lib/forms/dirty-state'; @@ -35,12 +33,8 @@ export default function AdminPage() { const [isUploadingBanner, setIsUploadingBanner] = useState(false); const [isCheckingBannerStorage, setIsCheckingBannerStorage] = useState(false); const [bannerUploadError, setBannerUploadError] = useState(null); - const [showBannerSessionPrompt, setShowBannerSessionPrompt] = useState(false); const [showBannerStorageConfiguration, setShowBannerStorageConfiguration] = useState(false); const [pendingBannerFile, setPendingBannerFile] = useState(null); - const [bannerPassword, setBannerPassword] = useState(''); - const [bannerPromptError, setBannerPromptError] = useState(''); - const [isRefreshingBannerSession, setIsRefreshingBannerSession] = useState(false); const [isUploadingLogo, setIsUploadingLogo] = useState(false); const [logoUploadError, setLogoUploadError] = useState(null); const [isUploadingFavicon, setIsUploadingFavicon] = useState(false); @@ -127,21 +121,12 @@ export default function AdminPage() { setNodeSettings(nextSettings); await handleSaveSettings(nextSettings); setPendingBannerFile(null); - setShowBannerSessionPrompt(false); - setBannerPassword(''); - setBannerPromptError(''); } catch (error) { if (error instanceof MediaUploadError && error.code === 'STORAGE_NOT_CONFIGURED' && allowPrompt) { setPendingBannerFile(file); setShowBannerStorageConfiguration(true); return; } - if (error instanceof MediaUploadError && error.status === 401 && allowPrompt) { - setPendingBannerFile(file); - setBannerPromptError(''); - setShowBannerSessionPrompt(true); - return; - } console.error('Banner upload failed', error); setBannerUploadError(error instanceof Error ? error.message : 'Upload failed. Please try again.'); } finally { @@ -174,39 +159,6 @@ export default function AdminPage() { } }; - const handleBannerSessionSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - - if (!pendingBannerFile) { - setShowBannerSessionPrompt(false); - return; - } - - if (!bannerPassword.trim()) { - setBannerPromptError('Please enter your password'); - return; - } - - setIsRefreshingBannerSession(true); - setBannerPromptError(''); - - try { - await refreshStorageSession(bannerPassword.trim()); - await uploadBannerFile(pendingBannerFile, false); - } catch (error) { - setBannerPromptError(error instanceof Error ? error.message : 'Failed to confirm password'); - } finally { - setIsRefreshingBannerSession(false); - } - }; - - const handleBannerSessionCancel = () => { - setShowBannerSessionPrompt(false); - setPendingBannerFile(null); - setBannerPassword(''); - setBannerPromptError(''); - }; - const handleLogoUpload = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; event.target.value = ''; @@ -621,19 +573,6 @@ export default function AdminPage() { )} - { - setBannerPassword(nextPassword); - setBannerPromptError(''); - }} - onSubmit={handleBannerSessionSubmit} - onCancel={handleBannerSessionCancel} - /> { diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index fc3630b..5626ece 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -1,6 +1,5 @@ import { NextResponse } from 'next/server'; import { authenticateUser, createSession } from '@/lib/auth'; -import { createStorageSession } from '@/lib/storage/session'; import { verifyTurnstileToken } from '@/lib/turnstile'; import { z } from 'zod'; @@ -31,7 +30,6 @@ export async function POST(request: Request) { // Create session await createSession(user.id); - await createStorageSession(user, data.password); return NextResponse.json({ success: true, diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts index 1cfc6f2..3c3c70e 100644 --- a/src/app/api/auth/logout/route.ts +++ b/src/app/api/auth/logout/route.ts @@ -1,6 +1,5 @@ import { NextResponse } from 'next/server'; import { destroySession, getSession } from '@/lib/auth'; -import { clearStorageSession } from '@/lib/storage/session'; import { z } from 'zod'; const logoutSchema = z.object({ @@ -15,10 +14,6 @@ export async function POST(request: Request) { const targetUserId = data.userId ?? currentSession?.user.id; - if (targetUserId) { - await clearStorageSession(targetUserId); - } - await destroySession(targetUserId); return NextResponse.json({ success: true }); diff --git a/src/app/api/bots/route.ts b/src/app/api/bots/route.ts index 5a4ac48..8101d23 100644 --- a/src/app/api/bots/route.ts +++ b/src/app/api/bots/route.ts @@ -17,8 +17,6 @@ import { BotHandleTakenError, BotValidationError, } from '@/lib/bots/botManager'; -import { generateAndUploadAvatarToUserStorage } from '@/lib/storage/s3'; -import { createStorageSession, getStorageSession } from '@/lib/storage/session'; // Schema for creating a bot const createBotSchema = z.object({ @@ -45,7 +43,6 @@ const createBotSchema = z.object({ timezone: z.string().optional(), }).optional(), autonomousMode: z.boolean().optional(), - ownerPassword: z.string().optional(), }); /** @@ -62,37 +59,11 @@ export async function POST(request: Request) { const body = await request.json(); const data = createBotSchema.parse(body); - const storageSession = - (await getStorageSession(user.id)) || - (data.ownerPassword ? await createStorageSession(user, data.ownerPassword) : null); - - // Generate bot avatar using owner's S3 storage if no avatar URL - let botAvatarUrl: string | null | undefined = data.avatarUrl; - if (!botAvatarUrl && storageSession) { - try { - const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821'; - const botHandle = `${data.handle.toLowerCase()}@${nodeDomain}`; - - botAvatarUrl = await generateAndUploadAvatarToUserStorage( - botHandle, - storageSession.endpoint || undefined, - storageSession.publicBaseUrl || undefined, - storageSession.region || 'us-east-1', - storageSession.bucket, - storageSession.accessKeyId, - storageSession.secretAccessKey - ); - } catch (err) { - console.error('[Bot API] Failed to generate bot avatar:', err); - // Continue without avatar - user can set it later - } - } - const bot = await createBot(user.id, { name: data.name, handle: data.handle, bio: data.bio, - avatarUrl: botAvatarUrl ?? undefined, + avatarUrl: data.avatarUrl, headerUrl: data.headerUrl, personality: data.personality, llmProvider: data.llmProvider, diff --git a/src/app/api/media/upload/route.ts b/src/app/api/media/upload/route.ts deleted file mode 100644 index c6af549..0000000 --- a/src/app/api/media/upload/route.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { db, media } from '@/db'; -import { requireAuth } from '@/lib/auth'; -import { getStorageSession, createStorageSession } from '@/lib/storage/session'; -import { uploadWithStorageCredentials } from '@/lib/storage/s3'; -import { v4 as uuidv4 } from 'uuid'; -import { getMaxMediaSize, getMediaKind } from '@/lib/media/upload-policy'; - -export async function POST(req: NextRequest) { - try { - const user = await requireAuth(); - - const formData = await req.formData(); - const file = formData.get('file') as File | null; - const altText = (formData.get('alt') as string | null) || null; - const password = formData.get('password') as string | null; - - if (!file) { - return NextResponse.json({ error: 'No file provided' }, { status: 400 }); - } - - // Validate file type - const mediaKind = getMediaKind(file.type); - - if (mediaKind === 'unsupported') { - return NextResponse.json({ - error: 'Invalid file type. Upload an image, video, MP3, M4A, AAC, WAV, OGG, or FLAC file.' - }, { status: 400 }); - } - - // Validate file size based on type - const maxSize = getMaxMediaSize(file.type); - if (maxSize !== null && file.size > maxSize) { - return NextResponse.json({ - error: `File too large. Maximum size: ${mediaKind === 'image' ? '10MB' : '100MB'}` - }, { status: 400 }); - } - - // Check if user has S3 storage configured - if (!user.storageProvider || !user.storageAccessKeyEncrypted || !user.storageSecretKeyEncrypted) { - return NextResponse.json({ - error: 'Connect your storage before uploading media.', - code: 'STORAGE_NOT_CONFIGURED', - }, { status: 409 }); - } - - const storageSession = - (await getStorageSession(user.id)) || - (password ? await createStorageSession(user, password) : null); - - if (!storageSession) { - return NextResponse.json({ - error: 'Upload session expired. Please sign in again.' - }, { status: 401 }); - } - - const buffer = Buffer.from(await file.arrayBuffer()); - const filename = `${uuidv4()}-${file.name.replace(/[^a-zA-Z0-9.-]/g, '')}`; - - // Upload to user's own S3-compatible storage - const uploadResult = await uploadWithStorageCredentials( - buffer, - filename, - file.type, - storageSession.provider, - storageSession.endpoint, - storageSession.publicBaseUrl, - storageSession.region, - storageSession.bucket, - storageSession.accessKeyId, - storageSession.secretAccessKey - ); - - // Store media record with S3 URL - if (db) { - const [mediaRecord] = await db.insert(media).values({ - userId: user.id, - postId: null, - url: uploadResult.url, - altText, - mimeType: file.type, - width: 0, // TODO: Get actual dimensions - height: 0, - }).returning(); - - return NextResponse.json({ - success: true, - media: mediaRecord, - url: uploadResult.url, - key: uploadResult.key, - }); - } - - return NextResponse.json({ - success: true, - url: uploadResult.url, - key: uploadResult.key, - }); - - } catch (error) { - if (error instanceof Error && error.message === 'Authentication required') { - return NextResponse.json({ error: 'Authentication required' }, { status: 401 }); - } - if (error instanceof Error && error.message === 'Invalid storage password') { - return NextResponse.json({ - error: 'Incorrect password. Please try again.' - }, { status: 401 }); - } - if (error instanceof Error && error.message.includes('Storage')) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - console.error('Upload error:', error); - return NextResponse.json({ error: 'Upload failed' }, { status: 500 }); - } -} diff --git a/src/app/api/storage/configuration/route.test.ts b/src/app/api/storage/configuration/route.test.ts new file mode 100644 index 0000000..c2b6937 --- /dev/null +++ b/src/app/api/storage/configuration/route.test.ts @@ -0,0 +1,41 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const mocks = vi.hoisted(() => ({ + requireAuth: vi.fn(), + configuredStuffboxUrl: vi.fn(), + getStuffboxConnection: vi.fn(), +})); + +vi.mock('@/lib/auth', () => ({ requireAuth: mocks.requireAuth })); +vi.mock('@/lib/stuffbox/client', () => ({ configuredStuffboxUrl: mocks.configuredStuffboxUrl })); +vi.mock('@/lib/stuffbox/tokens', () => ({ getStuffboxConnection: mocks.getStuffboxConnection })); + +import { GET } from './route'; + +describe('GET /api/storage/configuration', () => { + beforeEach(() => { + mocks.requireAuth.mockReset(); + mocks.configuredStuffboxUrl.mockReset(); + mocks.getStuffboxConnection.mockReset(); + mocks.configuredStuffboxUrl.mockReturnValue('https://stuffbox.example'); + mocks.getStuffboxConnection.mockResolvedValue(null); + }); + + it('does not expose legacy S3 credentials as an active storage provider', async () => { + mocks.requireAuth.mockResolvedValue({ + id: 'user-1', + storageProvider: 's3', + storageBucket: 'legacy-bucket', + }); + + const response = await GET(); + + expect(response.status).toBe(200); + await expect(response.json()).resolves.toEqual({ + provider: null, + stuffboxAvailable: true, + stuffboxBaseUrl: null, + stuffboxUpdatedAt: null, + }); + }); +}); diff --git a/src/app/api/storage/configuration/route.ts b/src/app/api/storage/configuration/route.ts index 3678d32..1dd731f 100644 --- a/src/app/api/storage/configuration/route.ts +++ b/src/app/api/storage/configuration/route.ts @@ -1,35 +1,17 @@ import { NextResponse } from 'next/server'; -import { eq } from 'drizzle-orm'; -import { z } from 'zod'; -import { db, users } from '@/db'; -import { requireAuth, verifyPassword } from '@/lib/auth'; -import { encryptPrivateKey, serializeEncryptedKey } from '@/lib/crypto/private-key'; -import { testS3Credentials, type StorageProvider } from '@/lib/storage/s3'; -import { createStorageSession } from '@/lib/storage/session'; +import { requireAuth } from '@/lib/auth'; import { configuredStuffboxUrl } from '@/lib/stuffbox/client'; import { getStuffboxConnection } from '@/lib/stuffbox/tokens'; -const configurationSchema = z.object({ - password: z.string().min(1), - provider: z.enum(['s3', 'r2', 'b2', 'wasabi', 'contabo']), - endpoint: z.string().trim().nullable().optional(), - publicBaseUrl: z.string().trim().nullable().optional(), - region: z.string().trim().min(2), - bucket: z.string().trim().min(3), - accessKey: z.string().min(10), - secretKey: z.string().min(10), -}); - export async function GET() { try { const user = await requireAuth(); const stuffbox = await getStuffboxConnection(user.id); return NextResponse.json({ - provider: stuffbox ? 'stuffbox' : user.storageProvider ? 's3' : null, + provider: stuffbox ? 'stuffbox' : null, stuffboxAvailable: Boolean(configuredStuffboxUrl()), stuffboxBaseUrl: stuffbox?.baseUrl ?? null, stuffboxUpdatedAt: stuffbox?.updatedAt.toISOString() ?? null, - s3Provider: user.storageProvider ?? null, }); } catch (error) { if (error instanceof Error && error.message === 'Authentication required') { @@ -39,57 +21,3 @@ export async function GET() { return NextResponse.json({ error: 'Failed to load storage status' }, { status: 500 }); } } - -export async function POST(request: Request) { - try { - const user = await requireAuth(); - const data = configurationSchema.parse(await request.json()); - - if (!user.passwordHash || !(await verifyPassword(data.password, user.passwordHash))) { - return NextResponse.json({ error: 'Incorrect account password' }, { status: 401 }); - } - - const storageTest = await testS3Credentials( - data.endpoint || null, - data.region, - data.bucket, - data.accessKey, - data.secretKey - ); - - if (!storageTest.success) { - return NextResponse.json( - { error: `Storage connection failed: ${storageTest.error}` }, - { status: 400 } - ); - } - - const storageProvider = data.provider as StorageProvider; - const storageAccessKeyEncrypted = serializeEncryptedKey(encryptPrivateKey(data.accessKey, data.password)); - const storageSecretKeyEncrypted = serializeEncryptedKey(encryptPrivateKey(data.secretKey, data.password)); - const storageValues = { - storageProvider, - storageEndpoint: data.endpoint || null, - storagePublicBaseUrl: data.publicBaseUrl || null, - storageRegion: data.region, - storageBucket: data.bucket, - storageAccessKeyEncrypted, - storageSecretKeyEncrypted, - }; - - await db.update(users).set(storageValues).where(eq(users.id, user.id)); - await createStorageSession({ ...user, ...storageValues }, data.password); - - return NextResponse.json({ success: true }); - } catch (error) { - if (error instanceof z.ZodError) { - return NextResponse.json({ error: 'Invalid storage configuration', details: error.issues }, { status: 400 }); - } - if (error instanceof Error && error.message === 'Authentication required') { - return NextResponse.json({ error: 'Authentication required' }, { status: 401 }); - } - - console.error('Storage configuration error:', error); - return NextResponse.json({ error: 'Failed to configure storage' }, { status: 500 }); - } -} diff --git a/src/app/api/storage/session/route.ts b/src/app/api/storage/session/route.ts deleted file mode 100644 index 69ea6cf..0000000 --- a/src/app/api/storage/session/route.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NextResponse } from 'next/server'; -import { z } from 'zod'; -import { requireAuth, verifyPassword } from '@/lib/auth'; -import { clearStorageSession, createStorageSession } from '@/lib/storage/session'; - -const bodySchema = z.object({ - password: z.string().min(1), -}); - -export async function POST(request: Request) { - try { - const user = await requireAuth(); - const body = await request.json(); - const data = bodySchema.parse(body); - - if (!user.passwordHash) { - return NextResponse.json({ error: 'Account has no password set' }, { status: 400 }); - } - - const isValid = await verifyPassword(data.password, user.passwordHash); - - if (!isValid) { - return NextResponse.json({ error: 'Incorrect password' }, { status: 401 }); - } - - await createStorageSession(user, data.password); - return NextResponse.json({ success: true }); - } catch (error) { - if (error instanceof z.ZodError) { - return NextResponse.json({ error: 'Invalid input', details: error.issues }, { status: 400 }); - } - if (error instanceof Error && error.message === 'Authentication required') { - return NextResponse.json({ error: 'Authentication required' }, { status: 401 }); - } - - console.error('Storage session error:', error); - return NextResponse.json({ error: 'Failed to refresh upload session' }, { status: 500 }); - } -} - -export async function DELETE() { - await clearStorageSession(); - return NextResponse.json({ success: true }); -} diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index 741df2a..eb6eb6b 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -988,7 +988,7 @@ export default function ChatPage() {
- Connect Stuffbox or an S3-compatible bucket + Connect and manage Stuffbox
diff --git a/src/app/settings/storage/page.tsx b/src/app/settings/storage/page.tsx index abc61f7..3cf5be0 100644 --- a/src/app/settings/storage/page.tsx +++ b/src/app/settings/storage/page.tsx @@ -2,15 +2,14 @@ import { useCallback, useEffect, useState } from 'react'; import Link from 'next/link'; -import { Box, Cloud, ExternalLink, HardDrive } from 'lucide-react'; +import { Box, ExternalLink } from 'lucide-react'; import { ArrowLeftIcon } from '@/components/Icons'; import { StorageConfigurationPrompt } from '@/components/StorageConfigurationPrompt'; interface StorageStatus { - provider: 'stuffbox' | 's3' | null; + provider: 'stuffbox' | null; stuffboxAvailable: boolean; stuffboxBaseUrl: string | null; - s3Provider: string | null; } export default function StorageSettingsPage() { @@ -68,10 +67,9 @@ export default function StorageSettingsPage() {
- {status?.provider === 'stuffbox' ? : status?.provider === 's3' ? : } +
-
{!status ? 'Loading…' : status.provider === 'stuffbox' ? 'Stuffbox.xyz connected' : status.provider === 's3' ? 'S3 storage connected' : 'No media storage connected'}
- {status?.s3Provider && status.provider === 's3' &&
Provider: {status.s3Provider}
} +
{!status ? 'Loading…' : status.provider === 'stuffbox' ? 'Stuffbox.xyz connected' : 'Stuffbox not connected'}
{status?.provider === 'stuffbox' && ( @@ -89,10 +87,10 @@ export default function StorageSettingsPage() { {status?.provider !== 'stuffbox' && (

- {status?.provider ? 'Change storage' : 'Connect media storage'} + Connect Stuffbox

- Choose Stuffbox for the simplest setup, or connect an S3-compatible bucket you already own. + Connect the official Synapsis storage partner to upload portable media from your account.

{}} variant="inline" />
diff --git a/src/components/StorageConfigurationPrompt.tsx b/src/components/StorageConfigurationPrompt.tsx index 62ced84..c383b20 100644 --- a/src/components/StorageConfigurationPrompt.tsx +++ b/src/components/StorageConfigurationPrompt.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useRef, useState } from 'react'; -import { Box, ChevronDown, ExternalLink } from 'lucide-react'; +import { Box, ExternalLink } from 'lucide-react'; import { hasNewStuffboxConnection } from '@/lib/stuffbox/browser-upload'; import { monitorStuffboxConnection, @@ -17,19 +17,9 @@ interface StorageConfigurationPromptProps { } export function StorageConfigurationPrompt({ open, onConfigured, onCancel, variant = 'modal' }: StorageConfigurationPromptProps) { - const [provider, setProvider] = useState('r2'); - const [endpoint, setEndpoint] = useState(''); - const [publicBaseUrl, setPublicBaseUrl] = useState(''); - const [region, setRegion] = useState('auto'); - const [bucket, setBucket] = useState(''); - const [accessKey, setAccessKey] = useState(''); - const [secretKey, setSecretKey] = useState(''); - const [password, setPassword] = useState(''); const [error, setError] = useState(''); - const [isSubmitting, setIsSubmitting] = useState(false); const [isLoading, setIsLoading] = useState(false); const [stuffboxAvailable, setStuffboxAvailable] = useState(false); - const [showS3, setShowS3] = useState(false); const [isConnectingStuffbox, setIsConnectingStuffbox] = useState(false); const connectionAbortRef = useRef(null); const connectionPopupRef = useRef(null); @@ -76,8 +66,6 @@ export function StorageConfigurationPrompt({ open, onConfigured, onCancel, varia }, [open]); if (!open) return null; - const needsCustomUrls = provider === 'r2' || provider === 'b2' || provider === 'contabo'; - const connectStuffbox = async () => { setError(''); setIsConnectingStuffbox(true); @@ -149,33 +137,13 @@ export function StorageConfigurationPrompt({ open, onConfigured, onCancel, varia } }; - const connectS3 = async (event: React.FormEvent) => { - event.preventDefault(); - setError(''); - setIsSubmitting(true); - try { - const response = await fetch('/api/storage/configuration', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ provider, endpoint: endpoint || null, publicBaseUrl: publicBaseUrl || null, region, bucket, accessKey, secretKey, password }), - }); - const data = await response.json().catch(() => ({})); - if (!response.ok) throw new Error(data.error || 'Failed to connect storage'); - await onConfigured(); - } catch (submitError) { - setError(submitError instanceof Error ? submitError.message : 'Failed to connect storage'); - } finally { - setIsSubmitting(false); - } - }; - const content = ( <> {variant === 'modal' && ( <> -

Connect media storage

+

Connect Stuffbox

- Your media lives outside this Synapsis node, so your account stays portable. + Synapsis uses Stuffbox for portable, user-owned media storage.

)} @@ -190,7 +158,7 @@ export function StorageConfigurationPrompt({ open, onConfigured, onCancel, varia
- {isConnectingStuffbox && ( @@ -200,39 +168,9 @@ export function StorageConfigurationPrompt({ open, onConfigured, onCancel, varia )} - - - {showS3 && ( -
-

- Advanced option. Credentials are encrypted locally; you may need to confirm your password again after the node restarts. -

- - {needsCustomUrls &&
- - -
} -
- - -
- - - - - - )} - {error &&
{error}
} {variant === 'modal' && ( -
+
)} ); @@ -247,10 +185,3 @@ export function StorageConfigurationPrompt({ open, onConfigured, onCancel, varia ); } - -function Field({ label, value, onChange, type = 'text', placeholder, minLength }: { label: string; value: string; onChange: (value: string) => void; type?: string; placeholder?: string; minLength?: number }) { - return ; -} diff --git a/src/components/StorageSessionPrompt.tsx b/src/components/StorageSessionPrompt.tsx deleted file mode 100644 index 7335a17..0000000 --- a/src/components/StorageSessionPrompt.tsx +++ /dev/null @@ -1,97 +0,0 @@ -'use client'; - -interface StorageSessionPromptProps { - open: boolean; - isSubmitting: boolean; - password: string; - error: string; - title?: string; - description?: string; - onPasswordChange: (password: string) => void; - onSubmit: (event: React.FormEvent) => void; - onCancel: () => void; -} - -export function StorageSessionPrompt({ - open, - isSubmitting, - password, - error, - title = 'Confirm your password', - description = 'Please confirm your password to continue uploading to your storage.', - onPasswordChange, - onSubmit, - onCancel, -}: StorageSessionPromptProps) { - if (!open) { - return null; - } - - return ( -
-
event.stopPropagation()} - > -

- {title} -

-

- {description} -

- -
-
- - onPasswordChange(event.target.value)} - autoFocus - /> -
- - {error && ( -
- {error} -
- )} - -
- - -
-
-
-
- ); -} diff --git a/src/components/UserStorageImageUpload.tsx b/src/components/UserStorageImageUpload.tsx index e490a9c..1ce5e17 100644 --- a/src/components/UserStorageImageUpload.tsx +++ b/src/components/UserStorageImageUpload.tsx @@ -1,9 +1,7 @@ 'use client'; import { useRef, useState } from 'react'; -import { StorageSessionPrompt } from '@/components/StorageSessionPrompt'; import { StorageConfigurationPrompt } from '@/components/StorageConfigurationPrompt'; -import { refreshStorageSession } from '@/lib/storage/client'; import { getStorageProvider, MediaUploadError, uploadMediaFile } from '@/lib/stuffbox/browser-upload'; interface UserStorageImageUploadProps { @@ -31,12 +29,8 @@ export function UserStorageImageUpload({ const [isUploading, setIsUploading] = useState(false); const [isCheckingStorage, setIsCheckingStorage] = useState(false); const [storageNotice, setStorageNotice] = useState(''); - const [showSessionPrompt, setShowSessionPrompt] = useState(false); const [showConfigurationPrompt, setShowConfigurationPrompt] = useState(false); const [pendingFile, setPendingFile] = useState(null); - const [password, setPassword] = useState(''); - const [promptError, setPromptError] = useState(''); - const [isRefreshingSession, setIsRefreshingSession] = useState(false); const resetFileInput = () => { if (inputRef.current) { @@ -53,21 +47,12 @@ export function UserStorageImageUpload({ onChange(media.url); onError?.(''); setPendingFile(null); - setShowSessionPrompt(false); - setPassword(''); - setPromptError(''); } catch (error) { if (error instanceof MediaUploadError && error.code === 'STORAGE_NOT_CONFIGURED' && allowPrompt) { setPendingFile(file); setShowConfigurationPrompt(true); return; } - if (error instanceof MediaUploadError && error.status === 401 && allowPrompt) { - setPendingFile(file); - setPromptError(''); - setShowSessionPrompt(true); - return; - } onError?.(error instanceof Error ? error.message : 'Upload failed'); } finally { setIsUploading(false); @@ -99,40 +84,6 @@ export function UserStorageImageUpload({ } }; - const handlePromptSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - - if (!pendingFile) { - setShowSessionPrompt(false); - return; - } - - if (!password.trim()) { - setPromptError('Please enter your password'); - return; - } - - setIsRefreshingSession(true); - setPromptError(''); - - try { - await refreshStorageSession(password.trim()); - await uploadFile(pendingFile, false); - } catch (error) { - setPromptError(error instanceof Error ? error.message : 'Failed to confirm password'); - } finally { - setIsRefreshingSession(false); - } - }; - - const handlePromptCancel = () => { - setShowSessionPrompt(false); - setPendingFile(null); - setPassword(''); - setPromptError(''); - resetFileInput(); - }; - return ( <>
@@ -195,18 +146,6 @@ export function UserStorageImageUpload({ )}
- { - setPassword(nextPassword); - setPromptError(''); - }} - onSubmit={handlePromptSubmit} - onCancel={handlePromptCancel} - /> { diff --git a/src/db/schema.ts b/src/db/schema.ts index f2e9a0a..da14408 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -67,14 +67,15 @@ export const users = sqliteTable('users', { movedTo: text('moved_to'), // New actor URL if this account migrated away movedFrom: text('moved_from'), // Old actor URL if this account migrated here migratedAt: integer('migrated_at', { mode: 'timestamp' }), // When the migration occurred - // Optional user-owned S3-compatible storage, configured on first media upload - storageProvider: text('storage_provider'), // 's3', 'r2', 'b2', 'wasabi', 'contabo' - storageEndpoint: text('storage_endpoint'), // S3 endpoint URL (optional for AWS) - storagePublicBaseUrl: text('storage_public_base_url'), // Public URL for viewing files (required for R2, B2, Contabo) - storageRegion: text('storage_region'), // Region (e.g., 'us-east-1') - storageBucket: text('storage_bucket'), // Bucket name - storageAccessKeyEncrypted: text('storage_access_key_encrypted'), // Encrypted access key - storageSecretKeyEncrypted: text('storage_secret_key_encrypted'), // Encrypted secret key + // Legacy S3 fields retained so upgrades do not require a destructive table rebuild. + // New media storage connections use Stuffbox exclusively. + storageProvider: text('storage_provider'), + storageEndpoint: text('storage_endpoint'), + storagePublicBaseUrl: text('storage_public_base_url'), + storageRegion: text('storage_region'), + storageBucket: text('storage_bucket'), + storageAccessKeyEncrypted: text('storage_access_key_encrypted'), + storageSecretKeyEncrypted: text('storage_secret_key_encrypted'), followersCount: integer('followers_count').default(0).notNull(), followingCount: integer('following_count').default(0).notNull(), postsCount: integer('posts_count').default(0).notNull(), diff --git a/src/lib/bots/botManager.ts b/src/lib/bots/botManager.ts index 83fcbb3..ecf0dc2 100644 --- a/src/lib/bots/botManager.ts +++ b/src/lib/bots/botManager.ts @@ -11,7 +11,6 @@ */ import { db, bots, users, botContentSources, botContentItems, botMentions, botActivityLogs, botRateLimits, follows } from '@/db'; -import { generateAndUploadAvatarToUserStorage } from '@/lib/storage/s3'; import { decryptPrivateKey, deserializeEncryptedKey } from '@/lib/crypto/private-key'; import { eq, and, count } from 'drizzle-orm'; import { generateKeyPair } from '@/lib/crypto/keys'; @@ -403,7 +402,7 @@ export async function createBot(ownerId: string, config: BotCreateInput): Promis throw new BotHandleTakenError(config.handle); } - // Get owner to access their S3 storage for bot avatar + // Confirm the owner still exists before creating the bot user. const owner = await db.query.users.findFirst({ where: { id: ownerId }, }); @@ -423,19 +422,7 @@ export async function createBot(ownerId: string, config: BotCreateInput): Promis const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821'; const botDid = `did:web:${nodeDomain}:users:${config.handle.toLowerCase()}`; - // Generate bot avatar using owner's S3 storage if no avatar provided - let botAvatarUrl = config.avatarUrl || null; - if (!botAvatarUrl && owner.storageAccessKeyEncrypted && owner.storageSecretKeyEncrypted && owner.storageBucket) { - try { - // We need the owner's password to decrypt S3 credentials - // Since we don't have the password here, we'll leave avatar null - // The frontend should handle avatar generation with the user's password - // Or we can generate a default avatar URL pattern - console.log('[BotManager] Bot avatar will need to be set via API with user password'); - } catch (err) { - console.error('[BotManager] Failed to generate bot avatar:', err); - } - } + const botAvatarUrl = config.avatarUrl || null; // Create the bot's user account first const [botUser] = await db.insert(users).values({ diff --git a/src/lib/storage/client.ts b/src/lib/storage/client.ts deleted file mode 100644 index 256d21a..0000000 --- a/src/lib/storage/client.ts +++ /dev/null @@ -1,13 +0,0 @@ -export async function refreshStorageSession(password: string): Promise { - const res = await fetch('/api/storage/session', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ password }), - }); - - const data = await res.json().catch(() => ({})); - - if (!res.ok) { - throw new Error(data.error || 'Failed to confirm password'); - } -} diff --git a/src/lib/storage/s3.ts b/src/lib/storage/s3.ts deleted file mode 100644 index 3a4a8eb..0000000 --- a/src/lib/storage/s3.ts +++ /dev/null @@ -1,246 +0,0 @@ -/** - * User-Owned S3-Compatible Storage Utilities - * - * Supports AWS S3, Cloudflare R2, Backblaze B2, Wasabi, and Contabo. - */ - -import { S3Client, PutObjectCommand, HeadBucketCommand } from '@aws-sdk/client-s3'; -import { decryptPrivateKey, deserializeEncryptedKey } from '@/lib/crypto/private-key'; - -export type StorageProvider = 's3' | 'r2' | 'b2' | 'wasabi' | 'contabo'; - -interface S3Credentials { - endpoint?: string; - region: string; - accessKeyId: string; - secretAccessKey: string; - bucket: string; -} - -interface StorageUploadResult { - url: string; - key: string; -} - -function buildStorageUrl( - key: string, - endpoint: string | null | undefined, - publicBaseUrl: string | null | undefined, - region: string, - bucket: string -): string { - if (publicBaseUrl) { - return `${publicBaseUrl.replace(/\/$/, '')}/${key}`; - } - - if (endpoint) { - return `${endpoint}/${bucket}/${key}`; - } - - return `https://${bucket}.s3.${region}.amazonaws.com/${key}`; -} - -/** - * Decrypt S3 credentials from encrypted storage - */ -export function decryptS3Credentials( - encryptedAccessKey: string, - encryptedSecretKey: string, - password: string -): { accessKeyId: string; secretAccessKey: string } { - try { - const accessKeyId = decryptPrivateKey( - deserializeEncryptedKey(encryptedAccessKey), - password - ); - const secretAccessKey = decryptPrivateKey( - deserializeEncryptedKey(encryptedSecretKey), - password - ); - return { accessKeyId, secretAccessKey }; - } catch { - throw new Error('Invalid storage password'); - } -} - -/** - * Create S3 client from credentials - */ -function createS3Client(creds: S3Credentials): S3Client { - return new S3Client({ - region: creds.region, - endpoint: creds.endpoint, - credentials: { - accessKeyId: creds.accessKeyId, - secretAccessKey: creds.secretAccessKey, - }, - forcePathStyle: !!creds.endpoint, // Needed for non-AWS S3-compatible services - }); -} - -/** - * Upload a file to user's S3-compatible storage - */ -export async function uploadToUserStorage( - file: Buffer, - filename: string, - mimeType: string, - provider: StorageProvider, - endpoint: string | null, - publicBaseUrl: string | null, - region: string, - bucket: string, - encryptedAccessKey: string, - encryptedSecretKey: string, - password: string -): Promise { - const { accessKeyId, secretAccessKey } = decryptS3Credentials( - encryptedAccessKey, - encryptedSecretKey, - password - ); - - return uploadWithStorageCredentials( - file, - filename, - mimeType, - provider, - endpoint, - publicBaseUrl, - region, - bucket, - accessKeyId, - secretAccessKey - ); -} - -export async function uploadWithStorageCredentials( - file: Buffer, - filename: string, - mimeType: string, - _provider: StorageProvider, - endpoint: string | null, - publicBaseUrl: string | null, - region: string, - bucket: string, - accessKeyId: string, - secretAccessKey: string -): Promise { - const normalizedRegion = region || 'us-east-1'; - - const s3 = createS3Client({ - endpoint: endpoint || undefined, - region: normalizedRegion, - accessKeyId, - secretAccessKey, - bucket, - }); - - const key = `synapsis/${filename}`; - - await s3.send(new PutObjectCommand({ - Bucket: bucket, - Key: key, - Body: file, - ContentType: mimeType, - })); - - const url = buildStorageUrl(key, endpoint, publicBaseUrl, normalizedRegion, bucket); - - return { url, key }; -} - -/** - * Test S3 credentials by attempting to head the bucket - */ -export async function testS3Credentials( - endpoint: string | null, - region: string, - bucket: string, - accessKeyId: string, - secretAccessKey: string -): Promise<{ success: boolean; error?: string }> { - try { - const s3 = createS3Client({ - endpoint: endpoint || undefined, - region, - accessKeyId, - secretAccessKey, - bucket, - }); - - // Try to check if bucket exists/is accessible - await s3.send(new HeadBucketCommand({ Bucket: bucket })); - return { success: true }; - } catch (error: any) { - console.error('[S3 Test] Credential test failed:', error); - - // Parse common errors - if (error.name === 'Forbidden' || error.name === '403') { - return { success: false, error: 'Access denied. Check your Access Key and Secret Key.' }; - } - if (error.name === 'NotFound' || error.name === '404') { - return { success: false, error: `Bucket "${bucket}" not found. Check the bucket name.` }; - } - if (error.name === 'NoSuchBucket') { - return { success: false, error: `Bucket "${bucket}" does not exist.` }; - } - if (error.name === 'InvalidAccessKeyId') { - return { success: false, error: 'Invalid Access Key ID.' }; - } - if (error.name === 'SignatureDoesNotMatch') { - return { success: false, error: 'Invalid Secret Access Key.' }; - } - if (error.name === 'NetworkingError' || error.name === 'ECONNREFUSED') { - return { success: false, error: 'Cannot connect to endpoint. Check your endpoint URL.' }; - } - - return { success: false, error: error.message || 'Failed to connect to storage. Please check your credentials.' }; - } -} - -/** - * Generate and upload avatar to user's S3 storage - */ -export async function generateAndUploadAvatarToUserStorage( - handle: string, - endpoint: string | undefined, - publicBaseUrl: string | undefined, - region: string, - bucket: string, - accessKey: string, - secretKey: string -): Promise { - try { - // 1. Fetch the avatar from DiceBear (PNG format for better compatibility) - const dicebearUrl = `https://api.dicebear.com/9.x/bottts-neutral/png?seed=${handle}`; - const response = await fetch(dicebearUrl); - - if (!response.ok) { - console.error(`Failed to fetch avatar from DiceBear: ${response.statusText}`); - return null; - } - - const arrayBuffer = await response.arrayBuffer(); - const buffer = Buffer.from(arrayBuffer); - - const result = await uploadWithStorageCredentials( - buffer, - `avatars/${handle.replace(/[^a-zA-Z0-9]/g, '')}-avatar.png`, - 'image/png', - 's3', - endpoint || null, - publicBaseUrl || null, - region, - bucket, - accessKey, - secretKey - ); - - return result.url; - - } catch (error) { - console.error('Error generating/uploading avatar:', error); - return null; - } -} diff --git a/src/lib/storage/session.ts b/src/lib/storage/session.ts deleted file mode 100644 index 8bc7ef5..0000000 --- a/src/lib/storage/session.ts +++ /dev/null @@ -1,187 +0,0 @@ -import crypto from 'crypto'; -import { cookies } from 'next/headers'; -import type { users } from '@/db'; -import { decryptS3Credentials, type StorageProvider } from '@/lib/storage/s3'; - -const STORAGE_SESSION_COOKIE = 'synapsis_storage_sessions'; -const STORAGE_SESSION_TTL_MS = 3650 * 24 * 60 * 60 * 1000; - -interface StorageSessionPayload { - userId: string; - provider: StorageProvider; - endpoint: string | null; - publicBaseUrl: string | null; - region: string; - bucket: string; - accessKeyId: string; - secretAccessKey: string; - expiresAt: number; -} - -type StorageSessionMap = Record; - -function getEncryptionKey(): Buffer { - const secret = process.env.AUTH_SECRET; - - if (!secret || secret.length < 16) { - throw new Error('AUTH_SECRET is not configured for storage sessions'); - } - - return crypto.createHash('sha256').update(secret).digest(); -} - -function encryptPayload(payload: StorageSessionPayload): string { - const key = getEncryptionKey(); - const iv = crypto.randomBytes(12); - const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); - const encrypted = Buffer.concat([ - cipher.update(JSON.stringify(payload), 'utf8'), - cipher.final(), - ]); - const tag = cipher.getAuthTag(); - - return Buffer.concat([iv, tag, encrypted]).toString('base64url'); -} - -function decryptPayload(token: string): StorageSessionPayload { - const key = getEncryptionKey(); - const raw = Buffer.from(token, 'base64url'); - - if (raw.length < 29) { - throw new Error('Invalid storage session token'); - } - - const iv = raw.subarray(0, 12); - const tag = raw.subarray(12, 28); - const encrypted = raw.subarray(28); - const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); - decipher.setAuthTag(tag); - - const decrypted = Buffer.concat([ - decipher.update(encrypted), - decipher.final(), - ]).toString('utf8'); - - return JSON.parse(decrypted) as StorageSessionPayload; -} - -function encryptPayloadMap(payload: StorageSessionMap): string { - return encryptPayload(payload as unknown as StorageSessionPayload); -} - -function decryptPayloadMap(token: string): StorageSessionMap { - return decryptPayload(token) as unknown as StorageSessionMap; -} - -async function readStorageSessionMap(): Promise { - const cookieStore = await cookies(); - const token = cookieStore.get(STORAGE_SESSION_COOKIE)?.value; - - if (!token) { - return {}; - } - - try { - const payload = decryptPayloadMap(token); - if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { - await clearStorageSession(); - return {}; - } - - return payload; - } catch { - await clearStorageSession(); - return {}; - } -} - -async function writeStorageSessionMap(payload: StorageSessionMap): Promise { - const cookieStore = await cookies(); - - if (Object.keys(payload).length === 0) { - cookieStore.delete(STORAGE_SESSION_COOKIE); - return; - } - - const maxExpiresAt = Math.max(...Object.values(payload).map(session => session.expiresAt)); - - cookieStore.set(STORAGE_SESSION_COOKIE, encryptPayloadMap(payload), { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'lax', - path: '/', - expires: new Date(maxExpiresAt), - }); -} - -export async function createStorageSession( - user: typeof users.$inferSelect, - password: string -): Promise { - if ( - !user.storageProvider || - !user.storageAccessKeyEncrypted || - !user.storageSecretKeyEncrypted || - !user.storageBucket - ) { - await clearStorageSession(user.id); - return null; - } - - const { accessKeyId, secretAccessKey } = decryptS3Credentials( - user.storageAccessKeyEncrypted, - user.storageSecretKeyEncrypted, - password - ); - - const payload: StorageSessionPayload = { - userId: user.id, - provider: user.storageProvider as StorageProvider, - endpoint: user.storageEndpoint, - publicBaseUrl: user.storagePublicBaseUrl, - region: user.storageRegion || 'us-east-1', - bucket: user.storageBucket, - accessKeyId, - secretAccessKey, - expiresAt: Date.now() + STORAGE_SESSION_TTL_MS, - }; - - const existingSessions = await readStorageSessionMap(); - existingSessions[user.id] = payload; - await writeStorageSessionMap(existingSessions); - - return payload; -} - -export async function getStorageSession(userId: string): Promise { - const sessions = await readStorageSessionMap(); - const payload = sessions[userId]; - - if (!payload) { - return null; - } - - if (payload.expiresAt <= Date.now()) { - delete sessions[userId]; - await writeStorageSessionMap(sessions); - return null; - } - - return payload; -} - -export async function clearStorageSession(userId?: string): Promise { - if (!userId) { - const cookieStore = await cookies(); - cookieStore.delete(STORAGE_SESSION_COOKIE); - return; - } - - const sessions = await readStorageSessionMap(); - if (!(userId in sessions)) { - return; - } - - delete sessions[userId]; - await writeStorageSessionMap(sessions); -} diff --git a/src/lib/stuffbox/browser-upload.test.ts b/src/lib/stuffbox/browser-upload.test.ts index d18072a..cec3b28 100644 --- a/src/lib/stuffbox/browser-upload.test.ts +++ b/src/lib/stuffbox/browser-upload.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { hasNewStuffboxConnection } from './browser-upload'; +import { getStorageProvider, hasNewStuffboxConnection } from './browser-upload'; afterEach(() => vi.unstubAllGlobals()); @@ -23,3 +23,12 @@ describe('hasNewStuffboxConnection', () => { }); }); +describe('getStorageProvider', () => { + it('does not accept a legacy S3 provider', async () => { + vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({ + provider: 's3', + }), { status: 200 }))); + + await expect(getStorageProvider()).resolves.toBeNull(); + }); +}); diff --git a/src/lib/stuffbox/browser-upload.ts b/src/lib/stuffbox/browser-upload.ts index 7f0d860..2626163 100644 --- a/src/lib/stuffbox/browser-upload.ts +++ b/src/lib/stuffbox/browser-upload.ts @@ -29,7 +29,7 @@ interface JsonResponse extends Record { stuffboxUpdatedAt?: string | null; } -export type StorageProvider = 'stuffbox' | 's3' | null; +export type StorageProvider = 'stuffbox' | null; async function json(response: Response): Promise { return response.json().catch(() => ({})) as Promise; @@ -41,9 +41,7 @@ export async function getStorageProvider(): Promise { if (!response.ok) { throw new MediaUploadError(configuration.error || 'Unable to load storage configuration', undefined, response.status); } - return configuration.provider === 'stuffbox' || configuration.provider === 's3' - ? configuration.provider - : null; + return configuration.provider === 'stuffbox' ? 'stuffbox' : null; } export async function hasNewStuffboxConnection(startedAt: string): Promise { @@ -119,23 +117,11 @@ async function uploadToStuffbox(file: File, onProgress?: (progress: number) => v return complete.media as UploadedMedia; } -async function uploadToS3(file: File): Promise { - const formData = new FormData(); - formData.append('file', file); - const response = await fetch('/api/media/upload', { method: 'POST', body: formData }); - const data = await json(response); - if (!response.ok || !data.media?.url) { - throw new MediaUploadError(data.error || 'Upload failed', data.code, response.status); - } - return data.media as UploadedMedia; -} - export async function uploadMediaFile( file: File, onProgress?: (progress: number) => void, ): Promise { const provider = await getStorageProvider(); if (provider === 'stuffbox') return uploadToStuffbox(file, onProgress); - if (provider === 's3') return uploadToS3(file); - throw new MediaUploadError('Connect media storage before uploading.', 'STORAGE_NOT_CONFIGURED', 409); + throw new MediaUploadError('Connect Stuffbox before uploading.', 'STORAGE_NOT_CONFIGURED', 409); }