Skip to content

feat(s3): S3-compatible upload target for scans - #1705

Open
manuc66 wants to merge 3 commits into
masterfrom
split/s3-delivery
Open

feat(s3): S3-compatible upload target for scans#1705
manuc66 wants to merge 3 commits into
masterfrom
split/s3-delivery

Conversation

@manuc66

@manuc66 manuc66 commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Problem

Scans could only be delivered to Paperless-ngx and Nextcloud. Users on AWS S3, MinIO, Cloudflare R2 or Wasabi had no way to store scans in object storage.

Solution

A new S3 delivery target:

  • SigV4 request signing via built-in node:crypto (no new dependency).
  • Path-style and virtual-host addressing, optional bucket prefix and STS session token.
  • Uploads individual images or the merged PDF, wired into postProcessing alongside Paperless/Nextcloud.
  • Configured via --s3-* CLI options, s3_* config-file keys or S3_* Docker env vars.

Included robustness fixes (each with a failing-test-first commit):

  • \ separators in --s3-prefix normalized to / (Windows).
  • Empty --s3-session-token treated as absent (no x-amz-security-token: "").
  • IP endpoints force path-style addressing automatically (virtual-host scans.192.168.1.100 would fail DNS).
  • S3_FORCE_PATH_STYLE=false / =0 no longer enables the flag in Docker.

Review checklist

Already fixed in this PR (verify):

  • Windows \ separators in --s3-prefix normalized to / (buildObjectKey).
  • Empty --s3-session-token treated as absent (no empty x-amz-security-token header, no signed-header entry).
  • IP endpoints force path-style addressing (virtual-host scans.192.168.1.100 would fail DNS).
  • S3_FORCE_PATH_STYLE=false / =0 no longer enables the flag in Docker.

Follow-ups to realize in this PR:

  • Partial S3 configuration (--s3-url without bucket or key) is ignored silently — getS3Config now throws with the list of missing options.
  • Document the R2/Wasabi region specifics: Cloudflare R2 requires --s3-region auto; the default us-east-1 produces confusing SigV4 errors. Add a per-provider README example.
  • Extend test/s3.test.ts's independent signature re-computation to cover the STS path (x-amz-security-token).
  • Unify MIME inference (contentTypeForExtension) with the webhook descriptor (shared module once the webhook PR lands).

Themes to reflect on:

  • The S3 transport is a from-scratch SigV4 implementation — review the canonical-request construction and signed-headers subset against the spec, and run the real-services harness (MinIO) before merging.
  • keepFiles is global across targets; per-target granularity is a follow-up in the webhook PR.

Testing

  • test/s3.test.ts: SigV4 signature re-verified server-side by an independent recomputation; path-style + virtual-host, images + PDF, error handling, prefix normalization, empty session token, IP endpoints.
  • Full suite green (683 passing), README help snapshots regenerated.

Merge order

Merge after the processing-pipeline PR (foundation). The webhook/outbox PR follows and depends on the S3 file-location helpers.

Adds an S3 delivery target (AWS S3, MinIO, Cloudflare R2, Wasabi...):
- SigV4 request signing with no new dependency (src/s3/s3.ts)
- path-style and virtual-host addressing, bucket prefix, optional STS
  session token (src/s3/S3Config.ts)
- CLI options (--s3-*), config file keys (s3_*), Docker env (S3_*)
- upload of individual images or merged PDFs wired into postProcessing

Also fixes, verified by tests:
- Windows \ separators in --s3-prefix normalized to /
- empty --s3-session-token treated as absent (no empty header)
- IP endpoints force path-style addressing automatically
- S3_FORCE_PATH_STYLE=false / =0 no longer enables the flag in Docker

Ships test/s3.test.ts and README/CHANGELOG entries.
A partial S3 configuration (--s3-url without bucket or credentials) was
ignored silently: no warning, no error, uploads never happened, and the
webhook still reported scan-completed without the s3 delivery. getS3Config
now throws with the list of missing options as soon as any S3 option is
provided but the configuration is incomplete (mirroring the webhook auth
guard).
The independent signature re-computation in test/s3.test.ts only covered
host/x-amz-content-sha256/x-amz-date. It now also folds in
x-amz-security-token when present, and a dedicated case uploads with an
STS session token to prove the signed-headers list matches.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant