Files
Paul HittandClaude Opus 5.5 ea1b2f228e Stream attachment uploads and downloads through storage
Uploads are authenticated before the body is read: token, proof headers,
device and account are checked first, then Content-Length (required, 411
without it) against attachmentMaxBytes and the rate limits. The body then
streams to storage while it is counted and hashed. The proof signature,
which covers the body's SHA-256, is checked after the last byte, and a
blob whose proof fails, or whose body ends short of Content-Length, is
deleted.

S3 PUTs are signed UNSIGNED-PAYLOAD and sent with a known length through
AsyncHTTPClient. The digest is unknown until the last byte, so
x-amz-checksum-sha256 cannot go in a header, and B2 refuses aws-chunked.
GETs stream the bucket's body to the client, Range still asked of the
bucket. The disk store writes to a .partial file and renames it.

Vapor runs a streamed body's closure a second time when writing the
response fails; the bucket body is handed out once, because a second read
traps the process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:49:39 -04:00

56 lines
2.6 KiB
Swift

// swift-tools-version: 6.0
import PackageDescription
// Vapor for the API, Fluent for the ORM, Leaf for the landing page, and swift-
// crypto for the one cryptographic operation this service performs: verifying
// an Ed25519 signature.
let package = Package(
name: "flamenet-relay",
platforms: [.macOS(.v13)],
products: [
.executable(name: "flamenet-relay", targets: ["FlamenetRelay"])
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.106.0"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.9.0"),
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.7.0"),
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.8.0"),
.package(url: "https://github.com/vapor/leaf.git", from: "4.3.0"),
.package(url: "https://github.com/apple/swift-crypto.git", from: "3.0.0"),
// Wake-up fan-out for long-polling.
.package(url: "https://github.com/vapor/redis.git", from: "4.11.0"),
// Already in the graph through Vapor. Named here because the
// attachment store streams through its request and response types,
// which Vapor does not re-export.
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.21.0"),
],
targets: [
.executableTarget(
name: "FlamenetRelay",
dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
.product(name: "Redis", package: "redis"),
.product(name: "Leaf", package: "leaf"),
.product(name: "Crypto", package: "swift-crypto"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
]
),
.testTarget(
name: "FlamenetRelayTests",
dependencies: [
"FlamenetRelay",
.product(name: "VaporTesting", package: "vapor"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
// Only PostgresMigrationTests uses it, and only when
// FN_TEST_POSTGRES names a reachable server: CI has no
// Postgres service, so the dialect production runs on is
// otherwise never migrated by any test.
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
]
),
]
)