* Migrate S3 storage module with unit and integration tests
- Migrate S3 CRUD and image operations to packages/api/src/storage/s3/
- Add S3ImageService class with dependency injection
- Add unit tests using aws-sdk-client-mock
- Add integration tests with real s3 bucket (condition presence of AWS_TEST_BUCKET_NAME)
* AI Review Findings Fixes
* chore: tests and refactor S3 storage types
- Added mock implementations for the 'sharp' library in various test files to improve image processing testing.
- Updated type references in S3 storage files from MongoFile to TFile for consistency and type safety.
- Refactored S3 CRUD operations to ensure proper handling of file types and improve code clarity.
- Enhanced integration tests to validate S3 file operations and error handling more effectively.
* chore: rename test file
* Remove duplicate import of refreshS3Url
* chore: imports order
* fix: remove duplicate imports for S3 URL handling in UserController
* fix: remove duplicate import of refreshS3FileUrls in files.js
* test: Add mock implementations for 'sharp' and '@librechat/api' in UserController tests
- Introduced mock functions for the 'sharp' library to facilitate image processing tests, including metadata retrieval and buffer conversion.
- Enhanced mocking for '@librechat/api' to ensure consistent behavior in tests, particularly for the needsRefresh and getNewS3URL functions.
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
* fix: preserve ban data object in checkBan to prevent permanent cache
The !! operator on line 108 coerces the ban data object to a boolean,
losing the expiresAt property. This causes:
1. Number(true.expiresAt) = NaN → expired bans never cleaned from banLogs
2. banCache.set(key, true, NaN) → Keyv stores with expires: null (permanent)
3. IP-based cache entries persist indefinitely, blocking unrelated users
Fix: replace isBanned (boolean) with banData (original object) so that
expiresAt is accessible for TTL calculation and proper cache expiry.
* fix: address checkBan cleanup defects exposed by ban data fix
The prior commit correctly replaced boolean coercion with the ban data
object, but activated previously-dead cleanup code with several defects:
- IP-only expired bans fell through cleanup without returning next(),
re-caching with negative TTL (permanent entry) and blocking the user
- Redis deployments used cache-prefixed keys for banLogs.delete(),
silently failing since bans are stored at raw keys
- banCache.set() calls were fire-and-forget, silently dropping errors
- No guard for missing/invalid expiresAt reproduced the NaN TTL bug
on legacy ban records
Consolidate expired-ban cleanup into a single block that always returns
next(), use raw keys (req.ip, userId) for banLogs.delete(), add an
expiresAt validity guard, await cache writes with error logging, and
parallelize independent I/O with Promise.all.
Add 25 tests covering all checkBan code paths including the specific
regressions for IP-only cleanup, Redis key mismatch, missing expiresAt,
and cache write failures.
---------
Co-authored-by: Danny Avila <danny@librechat.ai>