mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
Sign avatar download URLs against a browser-reachable endpoint
This commit is contained in:
@@ -260,17 +260,25 @@ and retrieving photos via an S3-compatible object store.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
SEAWEEDFS_SERVICE_URL=http://seaweedfs:8333 # Required, no default
|
SEAWEEDFS_SERVICE_URL=http://seaweedfs:8333 # Required, no default
|
||||||
|
SEAWEEDFS_PUBLIC_URL=http://localhost:8333 # Optional, defaults to SEAWEEDFS_SERVICE_URL
|
||||||
SEAWEEDFS_ACCESS_KEY_ID=admin # Required, no default
|
SEAWEEDFS_ACCESS_KEY_ID=admin # Required, no default
|
||||||
SEAWEEDFS_SECRET_ACCESS_KEY=secret # Required, no default
|
SEAWEEDFS_SECRET_ACCESS_KEY=secret # Required, no default
|
||||||
SEAWEEDFS_BUCKET=my-bucket # Required, no default
|
SEAWEEDFS_BUCKET=my-bucket # Required, no default
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Implementation**: `S3FileStorageProvider` throws on construction if any of
|
- **Implementation**: `S3FileStorageProvider` throws on construction if any of
|
||||||
these four values is missing
|
the four required values is missing
|
||||||
- **Local dev**: point at the `seaweedfs` Docker service (S3 API on port 8333)
|
- **Local dev**: point at the `seaweedfs` Docker service (S3 API on port 8333)
|
||||||
- **Production**: point at any S3-compatible endpoint (AWS S3, a hosted
|
- **Production**: point at any S3-compatible endpoint (AWS S3, a hosted
|
||||||
SeaweedFS cluster, and so on); `ForcePathStyle` is always enabled, which
|
SeaweedFS cluster, and so on); `ForcePathStyle` is always enabled, which
|
||||||
SeaweedFS requires and AWS S3 also accepts
|
SeaweedFS requires and AWS S3 also accepts
|
||||||
|
- **`SEAWEEDFS_PUBLIC_URL`**: the endpoint download URLs are signed against,
|
||||||
|
used by `GetPresignedUrl` and reaching the API response as
|
||||||
|
`PublicUserProfileDto.AvatarUrl`. A presigned URL is signed over its host, so
|
||||||
|
this has to be the address the browser uses (`http://localhost:8333` with the
|
||||||
|
dev compose file, which publishes the S3 API port), separate from the
|
||||||
|
in-network `SEAWEEDFS_SERVICE_URL` the API uploads through. The two can be the
|
||||||
|
same value wherever one address serves both
|
||||||
|
|
||||||
## Docker-specific variables
|
## Docker-specific variables
|
||||||
|
|
||||||
@@ -341,6 +349,7 @@ from the template in place; edit them manually if you go that route.
|
|||||||
| `SMTP_FROM_EMAIL` | ✓ | | ✓ | Yes | Email sender address |
|
| `SMTP_FROM_EMAIL` | ✓ | | ✓ | Yes | Email sender address |
|
||||||
| `SMTP_FROM_NAME` | ✓ | | ✓ | No | Defaults to `The Biergarten` |
|
| `SMTP_FROM_NAME` | ✓ | | ✓ | No | Defaults to `The Biergarten` |
|
||||||
| `SEAWEEDFS_SERVICE_URL` | ✓ | | ✓ | Yes | S3-compatible endpoint URL |
|
| `SEAWEEDFS_SERVICE_URL` | ✓ | | ✓ | Yes | S3-compatible endpoint URL |
|
||||||
|
| `SEAWEEDFS_PUBLIC_URL` | ✓ | | ✓ | No | Endpoint download URLs are signed against; defaults to `SEAWEEDFS_SERVICE_URL` |
|
||||||
| `SEAWEEDFS_ACCESS_KEY_ID` | ✓ | | ✓ | Yes | S3 access key |
|
| `SEAWEEDFS_ACCESS_KEY_ID` | ✓ | | ✓ | Yes | S3 access key |
|
||||||
| `SEAWEEDFS_SECRET_ACCESS_KEY` | ✓ | | ✓ | Yes | S3 secret key |
|
| `SEAWEEDFS_SECRET_ACCESS_KEY` | ✓ | | ✓ | Yes | S3 secret key |
|
||||||
| `SEAWEEDFS_BUCKET` | ✓ | | ✓ | Yes | S3 bucket name |
|
| `SEAWEEDFS_BUCKET` | ✓ | | ✓ | Yes | S3 bucket name |
|
||||||
@@ -415,6 +424,7 @@ SMTP_FROM_NAME=The Biergarten App
|
|||||||
|
|
||||||
# SeaweedFS (S3-compatible storage)
|
# SeaweedFS (S3-compatible storage)
|
||||||
SEAWEEDFS_SERVICE_URL=http://seaweedfs:8333
|
SEAWEEDFS_SERVICE_URL=http://seaweedfs:8333
|
||||||
|
SEAWEEDFS_PUBLIC_URL=http://localhost:8333
|
||||||
SEAWEEDFS_ACCESS_KEY_ID=<generated-with-openssl>
|
SEAWEEDFS_ACCESS_KEY_ID=<generated-with-openssl>
|
||||||
SEAWEEDFS_SECRET_ACCESS_KEY=<generated-with-openssl>
|
SEAWEEDFS_SECRET_ACCESS_KEY=<generated-with-openssl>
|
||||||
SEAWEEDFS_BUCKET=my-bucket
|
SEAWEEDFS_BUCKET=my-bucket
|
||||||
|
|||||||
@@ -68,8 +68,12 @@ WEBSITE_BASE_URL=http://localhost:3000
|
|||||||
# ======================
|
# ======================
|
||||||
# SeaweedFS (S3) Configuration
|
# SeaweedFS (S3) Configuration
|
||||||
# ======================
|
# ======================
|
||||||
# Credentials and default bucket for the local SeaweedFS S3-compatible storage
|
# Credentials and default bucket for the local SeaweedFS S3-compatible storage.
|
||||||
|
# SEAWEEDFS_SERVICE_URL is the in-network endpoint the API uploads through;
|
||||||
|
# SEAWEEDFS_PUBLIC_URL is the endpoint download URLs are signed against, so it has to be
|
||||||
|
# reachable from the browser. It defaults to SEAWEEDFS_SERVICE_URL when left blank.
|
||||||
SEAWEEDFS_SERVICE_URL=http://seaweedfs:8333
|
SEAWEEDFS_SERVICE_URL=http://seaweedfs:8333
|
||||||
|
SEAWEEDFS_PUBLIC_URL=http://localhost:8333
|
||||||
SEAWEEDFS_ACCESS_KEY_ID=admin
|
SEAWEEDFS_ACCESS_KEY_ID=admin
|
||||||
SEAWEEDFS_SECRET_ACCESS_KEY=secret
|
SEAWEEDFS_SECRET_ACCESS_KEY=secret
|
||||||
SEAWEEDFS_BUCKET=my-bucket
|
SEAWEEDFS_BUCKET=my-bucket
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ public static class ConfigurationKeys
|
|||||||
public const string SmtpFromName = "SMTP_FROM_NAME";
|
public const string SmtpFromName = "SMTP_FROM_NAME";
|
||||||
|
|
||||||
public const string SeaweedFsServiceUrl = "SEAWEEDFS_SERVICE_URL";
|
public const string SeaweedFsServiceUrl = "SEAWEEDFS_SERVICE_URL";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The browser-reachable base URL of the object store, used to sign download URLs. Falls back to
|
||||||
|
/// <see cref="SeaweedFsServiceUrl" /> when unset: SigV4 covers the host, so a URL signed against
|
||||||
|
/// the container-internal endpoint is only usable by callers that can resolve that host.
|
||||||
|
/// </summary>
|
||||||
|
public const string SeaweedFsPublicUrl = "SEAWEEDFS_PUBLIC_URL";
|
||||||
|
|
||||||
public const string SeaweedFsAccessKeyId = "SEAWEEDFS_ACCESS_KEY_ID";
|
public const string SeaweedFsAccessKeyId = "SEAWEEDFS_ACCESS_KEY_ID";
|
||||||
public const string SeaweedFsSecretAccessKey = "SEAWEEDFS_SECRET_ACCESS_KEY";
|
public const string SeaweedFsSecretAccessKey = "SEAWEEDFS_SECRET_ACCESS_KEY";
|
||||||
public const string SeaweedFsBucket = "SEAWEEDFS_BUCKET";
|
public const string SeaweedFsBucket = "SEAWEEDFS_BUCKET";
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ public class S3FileStorageProvider : IFileStorageProvider, IDisposable
|
|||||||
{
|
{
|
||||||
private readonly string _bucket;
|
private readonly string _bucket;
|
||||||
private readonly IAmazonS3 _client;
|
private readonly IAmazonS3 _client;
|
||||||
|
private readonly IAmazonS3 _presignClient;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="S3FileStorageProvider" /> class, reading storage
|
/// Initializes a new instance of the <see cref="S3FileStorageProvider" /> class, reading storage
|
||||||
/// configuration (<c>SEAWEEDFS_SERVICE_URL</c>, <c>SEAWEEDFS_ACCESS_KEY_ID</c>,
|
/// configuration (<c>SEAWEEDFS_SERVICE_URL</c>, <c>SEAWEEDFS_ACCESS_KEY_ID</c>,
|
||||||
/// <c>SEAWEEDFS_SECRET_ACCESS_KEY</c>, <c>SEAWEEDFS_BUCKET</c>) from <paramref name="configuration" />.
|
/// <c>SEAWEEDFS_SECRET_ACCESS_KEY</c>, <c>SEAWEEDFS_BUCKET</c>, and the optional
|
||||||
|
/// <c>SEAWEEDFS_PUBLIC_URL</c>) from <paramref name="configuration" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <exception cref="InvalidOperationException">
|
/// <exception cref="InvalidOperationException">
|
||||||
/// Thrown when any of the required configuration values is not set.
|
/// Thrown when any of the required configuration values is not set.
|
||||||
@@ -45,6 +47,23 @@ public class S3FileStorageProvider : IFileStorageProvider, IDisposable
|
|||||||
AmazonS3Config config = new() { ServiceURL = serviceUrl, ForcePathStyle = true };
|
AmazonS3Config config = new() { ServiceURL = serviceUrl, ForcePathStyle = true };
|
||||||
|
|
||||||
_client = new AmazonS3Client(accessKeyId, secretAccessKey, config);
|
_client = new AmazonS3Client(accessKeyId, secretAccessKey, config);
|
||||||
|
|
||||||
|
// A presigned URL is signed over its host, so one signed against the container-internal
|
||||||
|
// endpoint is only usable from inside the network. Sign downloads against the public
|
||||||
|
// endpoint instead, so a browser can fetch the object.
|
||||||
|
string? configuredPublicUrl = configuration[ConfigurationKeys.SeaweedFsPublicUrl];
|
||||||
|
string publicUrl = string.IsNullOrWhiteSpace(configuredPublicUrl)
|
||||||
|
? serviceUrl
|
||||||
|
: configuredPublicUrl;
|
||||||
|
|
||||||
|
_presignClient =
|
||||||
|
publicUrl == serviceUrl
|
||||||
|
? _client
|
||||||
|
: new AmazonS3Client(
|
||||||
|
accessKeyId,
|
||||||
|
secretAccessKey,
|
||||||
|
new AmazonS3Config { ServiceURL = publicUrl, ForcePathStyle = true }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -84,12 +103,15 @@ public class S3FileStorageProvider : IFileStorageProvider, IDisposable
|
|||||||
Expires = DateTime.UtcNow.Add(expiresIn),
|
Expires = DateTime.UtcNow.Add(expiresIn),
|
||||||
};
|
};
|
||||||
|
|
||||||
return _client.GetPreSignedURL(request);
|
return _presignClient.GetPreSignedURL(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
if (!ReferenceEquals(_presignClient, _client))
|
||||||
|
_presignClient.Dispose();
|
||||||
|
|
||||||
_client.Dispose();
|
_client.Dispose();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ services:
|
|||||||
DB_USER: "${DB_USER}"
|
DB_USER: "${DB_USER}"
|
||||||
DB_PASSWORD: "${DB_PASSWORD}"
|
DB_PASSWORD: "${DB_PASSWORD}"
|
||||||
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
||||||
|
SEAWEEDFS_PUBLIC_URL: "${SEAWEEDFS_PUBLIC_URL}"
|
||||||
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
||||||
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
||||||
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ services:
|
|||||||
DB_USER: "${DB_USER}"
|
DB_USER: "${DB_USER}"
|
||||||
DB_PASSWORD: "${DB_PASSWORD}"
|
DB_PASSWORD: "${DB_PASSWORD}"
|
||||||
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
||||||
|
SEAWEEDFS_PUBLIC_URL: "${SEAWEEDFS_PUBLIC_URL}"
|
||||||
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
||||||
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
||||||
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
||||||
@@ -110,6 +111,7 @@ services:
|
|||||||
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
||||||
WEBSITE_BASE_URL: "${WEBSITE_BASE_URL}"
|
WEBSITE_BASE_URL: "${WEBSITE_BASE_URL}"
|
||||||
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
||||||
|
SEAWEEDFS_PUBLIC_URL: "${SEAWEEDFS_PUBLIC_URL}"
|
||||||
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
||||||
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
||||||
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ services:
|
|||||||
DB_USER: "${DB_USER}"
|
DB_USER: "${DB_USER}"
|
||||||
DB_PASSWORD: "${DB_PASSWORD}"
|
DB_PASSWORD: "${DB_PASSWORD}"
|
||||||
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
||||||
|
SEAWEEDFS_PUBLIC_URL: "${SEAWEEDFS_PUBLIC_URL}"
|
||||||
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
||||||
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
||||||
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ services:
|
|||||||
DB_USER: "${DB_USER}"
|
DB_USER: "${DB_USER}"
|
||||||
DB_PASSWORD: "${DB_PASSWORD}"
|
DB_PASSWORD: "${DB_PASSWORD}"
|
||||||
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
||||||
|
SEAWEEDFS_PUBLIC_URL: "${SEAWEEDFS_PUBLIC_URL}"
|
||||||
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
||||||
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
||||||
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ services:
|
|||||||
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
CONFIRMATION_TOKEN_SECRET: "${CONFIRMATION_TOKEN_SECRET}"
|
||||||
WEBSITE_BASE_URL: "${WEBSITE_BASE_URL}"
|
WEBSITE_BASE_URL: "${WEBSITE_BASE_URL}"
|
||||||
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
||||||
|
SEAWEEDFS_PUBLIC_URL: "${SEAWEEDFS_PUBLIC_URL}"
|
||||||
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
||||||
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
||||||
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ services:
|
|||||||
DB_USER: "${DB_USER}"
|
DB_USER: "${DB_USER}"
|
||||||
DB_PASSWORD: "${DB_PASSWORD}"
|
DB_PASSWORD: "${DB_PASSWORD}"
|
||||||
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
SEAWEEDFS_SERVICE_URL: "${SEAWEEDFS_SERVICE_URL}"
|
||||||
|
SEAWEEDFS_PUBLIC_URL: "${SEAWEEDFS_PUBLIC_URL}"
|
||||||
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
SEAWEEDFS_ACCESS_KEY_ID: "${SEAWEEDFS_ACCESS_KEY_ID}"
|
||||||
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
SEAWEEDFS_SECRET_ACCESS_KEY: "${SEAWEEDFS_SECRET_ACCESS_KEY}"
|
||||||
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
SEAWEEDFS_BUCKET: "${SEAWEEDFS_BUCKET}"
|
||||||
|
|||||||
Reference in New Issue
Block a user