mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:41:09 +02:00
feat(storage): add public endpoint support for presigned URLs
When services run in Docker with internal endpoints (e.g., http://minio:9000), presigned URLs are inaccessible from browsers. This adds S3_PUBLIC_ENDPOINT support to generate presigned URLs using a publicly accessible endpoint (e.g., https://minio.mana.how) while keeping internal operations on the Docker network. Changes: - Add publicEndpoint to StorageConfig type - Create separate S3Client for presigned URL generation - Add S3_PUBLIC_ENDPOINT to factory configuration - Configure lightwrite-backend with public MinIO endpoint Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9dc6c111d3
commit
d6303e4998
4 changed files with 33 additions and 4 deletions
|
|
@ -609,6 +609,7 @@ services:
|
|||
MANA_CORE_AUTH_URL: http://mana-auth:3001
|
||||
CORS_ORIGINS: https://lightwrite.mana.how,https://mana.how
|
||||
S3_ENDPOINT: http://minio:9000
|
||||
S3_PUBLIC_ENDPOINT: https://minio.mana.how
|
||||
S3_REGION: us-east-1
|
||||
S3_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin}
|
||||
S3_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ import type {
|
|||
*/
|
||||
export class StorageClient {
|
||||
private client: S3Client;
|
||||
private presignClient: S3Client;
|
||||
private bucket: BucketConfig;
|
||||
|
||||
constructor(config: StorageConfig, bucket: BucketConfig) {
|
||||
// Main client for internal operations (upload, download, delete, etc.)
|
||||
this.client = new S3Client({
|
||||
endpoint: config.endpoint,
|
||||
region: config.region,
|
||||
|
|
@ -34,6 +36,20 @@ export class StorageClient {
|
|||
},
|
||||
forcePathStyle: config.forcePathStyle ?? true,
|
||||
});
|
||||
|
||||
// Separate client for presigned URLs (uses public endpoint if available)
|
||||
// This allows internal operations to use Docker network addresses
|
||||
// while presigned URLs use publicly accessible endpoints
|
||||
this.presignClient = new S3Client({
|
||||
endpoint: config.publicEndpoint ?? config.endpoint,
|
||||
region: config.region,
|
||||
credentials: {
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
},
|
||||
forcePathStyle: config.forcePathStyle ?? true,
|
||||
});
|
||||
|
||||
this.bucket = bucket;
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +158,7 @@ export class StorageClient {
|
|||
|
||||
/**
|
||||
* Generate a presigned URL for uploading (PUT)
|
||||
* Uses the public endpoint if configured, allowing browser access
|
||||
*/
|
||||
async getUploadUrl(key: string, options: PresignedUrlOptions = {}): Promise<string> {
|
||||
const command = new PutObjectCommand({
|
||||
|
|
@ -149,13 +166,14 @@ export class StorageClient {
|
|||
Key: key,
|
||||
});
|
||||
|
||||
return getSignedUrl(this.client, command, {
|
||||
return getSignedUrl(this.presignClient, command, {
|
||||
expiresIn: options.expiresIn ?? 3600,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a presigned URL for downloading (GET)
|
||||
* Uses the public endpoint if configured, allowing browser access
|
||||
*/
|
||||
async getDownloadUrl(key: string, options: PresignedUrlOptions = {}): Promise<string> {
|
||||
const command = new GetObjectCommand({
|
||||
|
|
@ -163,7 +181,7 @@ export class StorageClient {
|
|||
Key: key,
|
||||
});
|
||||
|
||||
return getSignedUrl(this.client, command, {
|
||||
return getSignedUrl(this.presignClient, command, {
|
||||
expiresIn: options.expiresIn ?? 3600,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type { StorageConfig, BucketConfig, BucketName } from './types';
|
|||
*/
|
||||
const ENV_KEYS = {
|
||||
ENDPOINT: 'S3_ENDPOINT',
|
||||
PUBLIC_ENDPOINT: 'S3_PUBLIC_ENDPOINT',
|
||||
REGION: 'S3_REGION',
|
||||
ACCESS_KEY: 'S3_ACCESS_KEY',
|
||||
SECRET_KEY: 'S3_SECRET_KEY',
|
||||
|
|
@ -29,15 +30,17 @@ const MINIO_DEFAULTS: StorageConfig = {
|
|||
*/
|
||||
export function getStorageConfig(): StorageConfig {
|
||||
const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
|
||||
const endpoint = process.env[ENV_KEYS.ENDPOINT] ?? (isDev ? MINIO_DEFAULTS.endpoint : '');
|
||||
|
||||
// Use environment variables if available, otherwise use MinIO defaults
|
||||
return {
|
||||
endpoint: process.env[ENV_KEYS.ENDPOINT] ?? (isDev ? MINIO_DEFAULTS.endpoint : ''),
|
||||
endpoint,
|
||||
publicEndpoint: process.env[ENV_KEYS.PUBLIC_ENDPOINT],
|
||||
region: process.env[ENV_KEYS.REGION] ?? MINIO_DEFAULTS.region,
|
||||
accessKeyId: process.env[ENV_KEYS.ACCESS_KEY] ?? (isDev ? MINIO_DEFAULTS.accessKeyId : ''),
|
||||
secretAccessKey:
|
||||
process.env[ENV_KEYS.SECRET_KEY] ?? (isDev ? MINIO_DEFAULTS.secretAccessKey : ''),
|
||||
forcePathStyle: isDev || process.env[ENV_KEYS.ENDPOINT]?.includes('localhost'),
|
||||
forcePathStyle: isDev || endpoint?.includes('localhost') || endpoint?.includes('minio'),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@ export interface StorageConfig {
|
|||
secretAccessKey: string;
|
||||
/** Force path-style URLs (required for MinIO) */
|
||||
forcePathStyle?: boolean;
|
||||
/**
|
||||
* Public endpoint for generating presigned URLs accessible from browsers.
|
||||
* Use this when the internal endpoint (e.g., http://minio:9000) differs
|
||||
* from the public URL (e.g., https://minio.mana.how).
|
||||
* If not set, presigned URLs use the main endpoint.
|
||||
*/
|
||||
publicEndpoint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue