diff --git a/migrations/multitenant/0013-s3-protocol-toggle.sql b/migrations/multitenant/0013-s3-protocol-toggle.sql new file mode 100644 index 00000000..92d30f0c --- /dev/null +++ b/migrations/multitenant/0013-s3-protocol-toggle.sql @@ -0,0 +1 @@ +ALTER TABLE tenants ADD COLUMN IF NOT EXISTS feature_s3_protocol boolean DEFAULT true NOT NULL; \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 47d9545d..bac846fb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -108,6 +108,7 @@ type StorageConfigType = { tusPartSize: number tusUseFileVersionSeparator: boolean defaultMetricsEnabled: boolean + s3ProtocolEnabled: boolean s3ProtocolPrefix: string s3ProtocolAllowForwardedHeader: boolean s3ProtocolEnforceRegion: boolean @@ -252,6 +253,7 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { getOptionalConfigFromEnv('TUS_USE_FILE_VERSION_SEPARATOR') === 'true', // S3 Protocol + s3ProtocolEnabled: getOptionalConfigFromEnv('S3_PROTOCOL_ENABLED') !== 'false', s3ProtocolPrefix: getOptionalConfigFromEnv('S3_PROTOCOL_PREFIX') || '', s3ProtocolAllowForwardedHeader: getOptionalConfigFromEnv('S3_ALLOW_FORWARDED_HEADER') === 'true', diff --git a/src/http/routes/admin/tenants.ts b/src/http/routes/admin/tenants.ts index 05e68565..37a79941 100644 --- a/src/http/routes/admin/tenants.ts +++ b/src/http/routes/admin/tenants.ts @@ -1,7 +1,7 @@ import { FastifyInstance, RequestGenericInterface } from 'fastify' import { FromSchema } from 'json-schema-to-ts' import apiKey from '../../plugins/apikey' -import { decrypt, encrypt } from '../../../internal/auth' +import { decrypt, encrypt } from '@internal/auth' import { deleteTenantConfig, TenantMigrationStatus, @@ -9,7 +9,7 @@ import { lastMigrationName, runMigrationsOnTenant, progressiveMigrations, -} from '../../../internal/database' +} from '@internal/database' import { dbSuperUser, storage } from '../../plugins' const patchSchema = { @@ -35,6 +35,12 @@ const patchSchema = { maxResolution: { type: 'number', nullable: true }, }, }, + s3Protocol: { + type: 'object', + properties: { + enabled: { type: 'boolean' }, + }, + }, }, }, }, @@ -75,6 +81,7 @@ interface tenantDBInterface { } | null service_key: string file_size_limit?: number + feature_s3_protocol?: boolean feature_image_transformation?: boolean image_transformation_max_resolution?: number } @@ -96,6 +103,7 @@ export default async function routes(fastify: FastifyInstance) { jwks, service_key, feature_image_transformation, + feature_s3_protocol, image_transformation_max_resolution, migrations_version, migrations_status, @@ -118,6 +126,9 @@ export default async function routes(fastify: FastifyInstance) { enabled: feature_image_transformation, maxResolution: image_transformation_max_resolution, }, + s3Protocol: { + enabled: feature_s3_protocol, + }, }, }) ) @@ -137,6 +148,7 @@ export default async function routes(fastify: FastifyInstance) { jwt_secret, jwks, service_key, + feature_s3_protocol, feature_image_transformation, image_transformation_max_resolution, migrations_version, @@ -163,6 +175,9 @@ export default async function routes(fastify: FastifyInstance) { enabled: feature_image_transformation, maxResolution: image_transformation_max_resolution, }, + s3Protocol: { + enabled: feature_s3_protocol, + }, }, migrationVersion: migrations_version, migrationStatus: migrations_status, @@ -197,6 +212,7 @@ export default async function routes(fastify: FastifyInstance) { jwks, service_key: encrypt(serviceKey), feature_image_transformation: features?.imageTransformation?.enabled ?? false, + feature_s3_protocol: features?.s3Protocol?.enabled ?? true, migrations_version: null, migrations_status: null, tracing_mode: tracingMode, @@ -250,6 +266,7 @@ export default async function routes(fastify: FastifyInstance) { jwks, service_key: serviceKey !== undefined ? encrypt(serviceKey) : undefined, feature_image_transformation: features?.imageTransformation?.enabled, + feature_s3_protocol: features?.s3Protocol?.enabled, image_transformation_max_resolution: features?.imageTransformation?.maxResolution === null ? null @@ -315,6 +332,10 @@ export default async function routes(fastify: FastifyInstance) { ?.image_transformation_max_resolution as number | undefined } + if (typeof features?.s3Protocol?.enabled !== 'undefined') { + tenantInfo.feature_s3_protocol = features?.s3Protocol?.enabled + } + if (databasePoolUrl) { tenantInfo.database_pool_url = encrypt(databasePoolUrl) } diff --git a/src/http/routes/s3/index.ts b/src/http/routes/s3/index.ts index 5dbd2f13..c1a4157d 100644 --- a/src/http/routes/s3/index.ts +++ b/src/http/routes/s3/index.ts @@ -1,11 +1,18 @@ import { FastifyInstance, RouteHandlerMethod } from 'fastify' import { JSONSchema } from 'json-schema-to-ts' import { trace } from '@opentelemetry/api' -import { db, jsonToXml, signatureV4, storage } from '../../plugins' +import { db, jsonToXml, requireTenantFeature, signatureV4, storage } from '../../plugins' import { findArrayPathsInSchemas, getRouter, RequestInput } from './router' import { s3ErrorHandler } from './error-handler' +import { getConfig } from '../../../config' + +const { s3ProtocolEnabled } = getConfig() export default async function routes(fastify: FastifyInstance) { + if (!s3ProtocolEnabled) { + return + } + fastify.register(async (fastify) => { const s3Router = getRouter() const s3Routes = s3Router.routes() @@ -97,6 +104,8 @@ export default async function routes(fastify: FastifyInstance) { } fastify.register(async (localFastify) => { + localFastify.register(requireTenantFeature('s3Protocol')) + const disableContentParser = routesByMethod?.some( (route) => route.disableContentTypeParser ) diff --git a/src/internal/database/tenant.ts b/src/internal/database/tenant.ts index 5d2896b0..35ec5653 100644 --- a/src/internal/database/tenant.ts +++ b/src/internal/database/tenant.ts @@ -41,6 +41,9 @@ export interface Features { enabled: boolean maxResolution?: number } + s3Protocol: { + enabled: boolean + } } export enum TenantMigrationStatus { @@ -203,6 +206,7 @@ export async function getTenantConfig(tenantId: string): Promise { jwks, service_key, feature_image_transformation, + feature_s3_protocol, image_transformation_max_resolution, database_pool_url, max_connections, @@ -231,6 +235,9 @@ export async function getTenantConfig(tenantId: string): Promise { enabled: feature_image_transformation, maxResolution: image_transformation_max_resolution, }, + s3Protocol: { + enabled: feature_s3_protocol, + }, }, migrationVersion: migrations_version, migrationStatus: migrations_status, diff --git a/src/test/tenant.test.ts b/src/test/tenant.test.ts index d25c0f0f..e5080ee3 100644 --- a/src/test/tenant.test.ts +++ b/src/test/tenant.test.ts @@ -23,6 +23,9 @@ const payload = { enabled: true, maxResolution: null, }, + s3Protocol: { + enabled: true, + }, }, } @@ -43,6 +46,9 @@ const payload2 = { enabled: false, maxResolution: null, }, + s3Protocol: { + enabled: true, + }, }, } diff --git a/src/test/x-forwarded-host.test.ts b/src/test/x-forwarded-host.test.ts index 7956b468..284bb48d 100644 --- a/src/test/x-forwarded-host.test.ts +++ b/src/test/x-forwarded-host.test.ts @@ -26,6 +26,9 @@ beforeAll(async () => { imageTransformation: { enabled: true, }, + s3Protocol: { + enabled: true, + }, }, }))