150 lines
7.7 KiB
C#
150 lines
7.7 KiB
C#
using Microsoft.Extensions.Options;
|
|
using Tiku.Application.Auth;
|
|
using Tiku.Application.Storage;
|
|
using Tiku.Infrastructure.Assets;
|
|
using Tiku.Infrastructure.Commerce;
|
|
using Tiku.Infrastructure.Storage;
|
|
|
|
namespace Tiku.Api.Configuration;
|
|
|
|
internal static class ExternalServiceOptionsExtensions
|
|
{
|
|
internal static IServiceCollection AddExternalServiceOptions(
|
|
this IServiceCollection services,
|
|
IConfiguration configuration,
|
|
IHostEnvironment environment)
|
|
{
|
|
services.Configure<ObjectStorageOptions>(
|
|
configuration.GetSection(ObjectStorageOptions.SectionName));
|
|
services.Configure<AliyunOssOptions>(
|
|
configuration.GetSection(AliyunOssOptions.SectionName));
|
|
services.Configure<S3CompatibleOptions>(
|
|
configuration.GetSection(S3CompatibleOptions.SectionName));
|
|
services.PostConfigure<ObjectStorageOptions>(options =>
|
|
{
|
|
options.DefaultProvider = configuration["STORAGE_DEFAULT_PROVIDER"] ?? options.DefaultProvider;
|
|
if (!environment.IsProduction() &&
|
|
options.DefaultProvider == ObjectStorageProviders.AliyunOss &&
|
|
!HasConfiguredAliyunOss(configuration))
|
|
options.DefaultProvider = ObjectStorageProviders.LocalDev;
|
|
options.DefaultBucket = configuration["STORAGE_DEFAULT_BUCKET"] ?? options.DefaultBucket;
|
|
options.PublicBaseUrl = configuration["STORAGE_PUBLIC_BASE_URL"] ?? options.PublicBaseUrl;
|
|
options.AllowedMimePrefixes = SplitLegacyList(
|
|
configuration["STORAGE_ALLOWED_MIME_PREFIXES"],
|
|
options.AllowedMimePrefixes);
|
|
options.AllowedMimeTypes = SplitLegacyList(
|
|
configuration["STORAGE_ALLOWED_MIME_TYPES"],
|
|
options.AllowedMimeTypes);
|
|
options.RequireTenantPrefix = bool.TryParse(
|
|
configuration["STORAGE_REQUIRE_TENANT_PREFIX"],
|
|
out var requireTenantPrefix)
|
|
? requireTenantPrefix
|
|
: options.RequireTenantPrefix;
|
|
options.MaxUploadBytes = long.TryParse(
|
|
configuration["STORAGE_MAX_UPLOAD_BYTES"],
|
|
out var maxUploadBytes)
|
|
? maxUploadBytes
|
|
: options.MaxUploadBytes;
|
|
});
|
|
services.PostConfigure<AliyunOssOptions>(options =>
|
|
{
|
|
options.Region = configuration["ALIYUN_OSS_REGION"] ?? options.Region;
|
|
options.Endpoint = configuration["ALIYUN_OSS_ENDPOINT"] ?? options.Endpoint;
|
|
options.AccessKeyId = configuration["ALIYUN_OSS_ACCESS_KEY_ID"] ?? options.AccessKeyId;
|
|
options.AccessKeySecret = configuration["ALIYUN_OSS_ACCESS_KEY_SECRET"] ?? options.AccessKeySecret;
|
|
options.SecurityToken = configuration["ALIYUN_OSS_STS_TOKEN"] ?? options.SecurityToken;
|
|
options.UseInternalEndpoint = bool.TryParse(
|
|
configuration["ALIYUN_OSS_INTERNAL"],
|
|
out var useInternalEndpoint)
|
|
? useInternalEndpoint
|
|
: options.UseInternalEndpoint;
|
|
});
|
|
services.PostConfigure<S3CompatibleOptions>(options =>
|
|
{
|
|
options.Endpoint = configuration["S3_ENDPOINT"] ?? options.Endpoint;
|
|
options.AccessKey = configuration["S3_ACCESS_KEY"] ?? options.AccessKey;
|
|
options.SecretKey = configuration["S3_SECRET_KEY"] ?? options.SecretKey;
|
|
options.Region = configuration["S3_REGION"] ?? options.Region;
|
|
options.Secure = bool.TryParse(configuration["S3_SECURE"], out var secure)
|
|
? secure
|
|
: options.Secure;
|
|
});
|
|
services.AddOptions<ObjectStorageOptions>()
|
|
.Validate(
|
|
options => !environment.IsProduction() ||
|
|
options.DefaultProvider == ObjectStorageProviders.AliyunOss,
|
|
"Production managed storage must use the configured Aliyun OSS provider.")
|
|
.ValidateOnStart();
|
|
services.AddOptions<AliyunOssOptions>()
|
|
.Validate<IOptions<ObjectStorageOptions>>(
|
|
(aliyun, storage) =>
|
|
!environment.IsProduction() ||
|
|
storage.Value.DefaultProvider != ObjectStorageProviders.AliyunOss ||
|
|
aliyun.IsConfigured,
|
|
"Aliyun OSS credentials and region or endpoint are required when it is the default provider.")
|
|
.ValidateOnStart();
|
|
services.AddOptions<S3CompatibleOptions>()
|
|
.Validate<IOptions<ObjectStorageOptions>>(
|
|
(s3, storage) =>
|
|
storage.Value.DefaultProvider != ObjectStorageProviders.LocalDev || s3.IsConfigured,
|
|
"S3-compatible endpoint and credentials are required when local_dev is the default provider.")
|
|
.ValidateOnStart();
|
|
services.AddOptions<ClamAvOptions>()
|
|
.Bind(configuration.GetSection(ClamAvOptions.SectionName))
|
|
.Validate(ClamAvOptions.BeValid, "ClamAV settings are invalid.")
|
|
.Validate<IOptions<ObjectStorageOptions>>(
|
|
(clamAv, storage) => clamAv.StreamMaxLength >= storage.Value.MaxUploadBytes,
|
|
"ClamAV StreamMaxLength must be greater than or equal to the storage max upload size.")
|
|
.ValidateOnStart();
|
|
|
|
services.AddOptions<TenantSecretEncryptionOptions>()
|
|
.Bind(configuration.GetSection(TenantSecretEncryptionOptions.SectionName))
|
|
.PostConfigure(options =>
|
|
{
|
|
options.KeyId = configuration["TIKU_TENANT_SECRET_KEY_ID"] ?? options.KeyId;
|
|
options.MasterKey = configuration["TIKU_TENANT_SECRET_MASTER_KEY"] ?? options.MasterKey;
|
|
})
|
|
.Validate(
|
|
TenantSecretEncryptionOptions.BeValid,
|
|
"Tenant secret encryption requires a key ID and a base64-encoded 32-byte master key.")
|
|
.Validate(
|
|
options => !environment.IsProduction() ||
|
|
!TenantSecretEncryptionOptions.IsDevelopmentDefault(options),
|
|
"Production tenant secret encryption cannot use the development master key.")
|
|
.ValidateOnStart();
|
|
|
|
services.AddOptions<SmsSecurityOptions>()
|
|
.Bind(configuration.GetSection(SmsSecurityOptions.SectionName))
|
|
.PostConfigure(options =>
|
|
{
|
|
options.CodePepper = configuration["TIKU_SMS_CODE_PEPPER"] ?? options.CodePepper;
|
|
})
|
|
.Validate(
|
|
SmsSecurityOptions.BeValid,
|
|
"SMS security requires a pepper of at least 32 characters, exactly five verification attempts, " +
|
|
"and positive tenant, phone, IP, and device rate limits.")
|
|
.ValidateOnStart();
|
|
|
|
return services;
|
|
}
|
|
|
|
private static string[] SplitLegacyList(string? value, string[] fallback)
|
|
{
|
|
return string.IsNullOrWhiteSpace(value)
|
|
? fallback
|
|
: value.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
|
}
|
|
|
|
private static bool HasConfiguredAliyunOss(IConfiguration configuration)
|
|
{
|
|
return !string.IsNullOrWhiteSpace(configuration["ALIYUN_OSS_ACCESS_KEY_ID"] ??
|
|
configuration["Storage:AliyunOss:AccessKeyId"]) &&
|
|
!string.IsNullOrWhiteSpace(configuration["ALIYUN_OSS_ACCESS_KEY_SECRET"] ??
|
|
configuration["Storage:AliyunOss:AccessKeySecret"]) &&
|
|
(!string.IsNullOrWhiteSpace(configuration["ALIYUN_OSS_REGION"] ??
|
|
configuration["Storage:AliyunOss:Region"]) ||
|
|
!string.IsNullOrWhiteSpace(configuration["ALIYUN_OSS_ENDPOINT"] ??
|
|
configuration["Storage:AliyunOss:Endpoint"]));
|
|
}
|
|
}
|