[storage] fix B2 / generic-S3 uploads ("S3 endpoint is required") — prepareUpload contract

Uploading to a Backblaze B2 (or any generic-S3) bucket failed with "S3 endpoint is
required." even though the connection tested fine.

Root cause: the two upload services have different prepareUpload contracts —
r2Service.prepareUpload(npub, { credentials }) vs s3UploadService.prepareUpload(settings).
MediaUploader calls both the r2 way, so for S3/B2 the npub STRING became the settings →
validateS3Connection saw no endpoint. Compounding it, a storageService.getConnection()
result keeps bucket/endpoint/region under `.meta`, which validateS3Connection reads at
the top level. (R2 worked because its service already uses the (npub, {credentials})
contract.)

Fix:
- s3UploadService.prepareUpload now accepts (npub, { credentials }) AND a direct settings
  object (back-compat), flattening meta → top-level via flattenS3Settings.
- The storage Test now derives/passes the B2 public URL (else the prefix), so it stops
  falsely warning "Public Bucket URL is missing. Uploads will fail."

Tests: tests/services/s3-upload-prepare-contract.test.mjs (3 — meta flatten, npub ignored
when credentials given, direct-settings back-compat). Build + lint clean; upload/storage
suites 46/46.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-30 16:02:37 -04:00
1 parent ffeafa90d5
commit 6e85c2a32b
4 files changed
+151 -1

No files matched your search

@@ -4,6 +4,7 @@ import {
prepareS3Connection,
getCorsOrigins,
deriveB2Endpoint,
derivePublicBaseUrl,
} from "../../services/s3Service.js";
import { getActiveSigner } from "../../nostr/client.js";
import { DEFAULT_NIP07_ENCRYPTION_METHODS } from "../../nostr/nip07Permissions.js";
@@ -952,6 +953,11 @@ export class ProfileStorageController {
} else {
config.endpoint = endpointOrAccount;
config.forcePathStyle = forcePathStyle;
// Explicit Public Access URL, else derive it (so the test doesn't falsely warn).
config.publicBaseUrl =
publicBaseUrl ||
derivePublicBaseUrl({ endpoint: endpointOrAccount, bucket, forcePathStyle });
config.baseDomain = config.publicBaseUrl;
}
try {