Make the Worker validate its service container and align Worker and Gateway hosts
The Worker now registers the Application layer, observability and stream tickets, validates the container on build and ships a complete appsettings. The Gateway drops underscore fields and uses a placeholder bucket in its default YARP transform.
This commit is contained in:
+2
-1
@@ -14,8 +14,8 @@ public static partial class MassTransitDependencyInjection
|
|||||||
public static IServiceCollection AddMassTransitClient(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddMassTransitClient(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
services.AddValidatedOptions<MassTransitConfiguration>(configuration);
|
services.AddValidatedOptions<MassTransitConfiguration>(configuration);
|
||||||
|
|
||||||
services.AddScoped<IEventBus, MassTransitEventBus>();
|
services.AddScoped<IEventBus, MassTransitEventBus>();
|
||||||
|
|
||||||
services.AddMassTransit(options =>
|
services.AddMassTransit(options =>
|
||||||
{
|
{
|
||||||
AddOutbox(options);
|
AddOutbox(options);
|
||||||
@@ -34,6 +34,7 @@ public static partial class MassTransitDependencyInjection
|
|||||||
services.AddValidatedOptions<MassTransitConfiguration>(configuration);
|
services.AddValidatedOptions<MassTransitConfiguration>(configuration);
|
||||||
|
|
||||||
services.AddValidatedOptions<WorkerConfiguration>(configuration);
|
services.AddValidatedOptions<WorkerConfiguration>(configuration);
|
||||||
|
services.AddScoped<IEventBus, MassTransitEventBus>();
|
||||||
|
|
||||||
RegisterRoutingSlipBuilders(services);
|
RegisterRoutingSlipBuilders(services);
|
||||||
|
|
||||||
|
|||||||
@@ -38,13 +38,6 @@ public static class ServicesDependencyInjection
|
|||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddUploadIntentConfiguration(this IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
services.AddValidatedOptions<UploadIntentConfiguration>(configuration);
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddJobs(this IServiceCollection services)
|
public static IServiceCollection AddJobs(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddHangfire((serviceProvider, hangfireConfiguration) =>
|
services.AddHangfire((serviceProvider, hangfireConfiguration) =>
|
||||||
|
|||||||
@@ -14,27 +14,24 @@ public sealed record TicketValidationResult(string Prefix, long? MaxBytes)
|
|||||||
|
|
||||||
public sealed class TicketValidator : IDisposable
|
public sealed class TicketValidator : IDisposable
|
||||||
{
|
{
|
||||||
private readonly StreamTicketValidationConfiguration _options;
|
private readonly RSA rsa;
|
||||||
private readonly RSA _rsa;
|
private readonly TokenValidationParameters validationParameters;
|
||||||
private readonly TokenValidationParameters _validationParameters;
|
private readonly JsonWebTokenHandler handler = new();
|
||||||
private readonly JsonWebTokenHandler _handler = new();
|
|
||||||
|
|
||||||
public TicketValidator(StreamTicketValidationConfiguration options)
|
public TicketValidator(StreamTicketValidationConfiguration configuration)
|
||||||
{
|
{
|
||||||
_options = options;
|
rsa = RSA.Create();
|
||||||
|
rsa.ImportFromPem(File.ReadAllText(configuration.PublicKeyPath));
|
||||||
|
|
||||||
_rsa = RSA.Create();
|
validationParameters = new TokenValidationParameters
|
||||||
_rsa.ImportFromPem(File.ReadAllText(_options.PublicKeyPath));
|
|
||||||
|
|
||||||
_validationParameters = new TokenValidationParameters
|
|
||||||
{
|
{
|
||||||
ValidateIssuer = true,
|
ValidateIssuer = true,
|
||||||
ValidIssuer = _options.Issuer,
|
ValidIssuer = configuration.Issuer,
|
||||||
ValidateAudience = true,
|
ValidateAudience = true,
|
||||||
ValidAudience = _options.Audience,
|
ValidAudience = configuration.Audience,
|
||||||
ValidateLifetime = true,
|
ValidateLifetime = true,
|
||||||
ValidateIssuerSigningKey = true,
|
ValidateIssuerSigningKey = true,
|
||||||
IssuerSigningKey = new RsaSecurityKey(_rsa),
|
IssuerSigningKey = new RsaSecurityKey(rsa),
|
||||||
ValidAlgorithms = [SecurityAlgorithms.RsaSha256],
|
ValidAlgorithms = [SecurityAlgorithms.RsaSha256],
|
||||||
ClockSkew = TimeSpan.FromSeconds(30),
|
ClockSkew = TimeSpan.FromSeconds(30),
|
||||||
};
|
};
|
||||||
@@ -45,7 +42,7 @@ public sealed class TicketValidator : IDisposable
|
|||||||
if (string.IsNullOrWhiteSpace(token))
|
if (string.IsNullOrWhiteSpace(token))
|
||||||
return TicketValidationResult.Invalid;
|
return TicketValidationResult.Invalid;
|
||||||
|
|
||||||
var result = await _handler.ValidateTokenAsync(token, _validationParameters);
|
var result = await handler.ValidateTokenAsync(token, validationParameters);
|
||||||
if (!result.IsValid)
|
if (!result.IsValid)
|
||||||
return TicketValidationResult.Invalid;
|
return TicketValidationResult.Invalid;
|
||||||
|
|
||||||
@@ -59,5 +56,5 @@ public sealed class TicketValidator : IDisposable
|
|||||||
return new TicketValidationResult(prefix, maxBytes);
|
return new TicketValidationResult(prefix, maxBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() => _rsa.Dispose();
|
public void Dispose() => rsa.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ using Musify.StreamingGateway.Authentication;
|
|||||||
using Musify.StreamingGateway.Configuration;
|
using Musify.StreamingGateway.Configuration;
|
||||||
using Musify.StreamingGateway.Middleware;
|
using Musify.StreamingGateway.Middleware;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder();
|
const string CorsPolicy = "media-clients";
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services
|
builder.Services
|
||||||
.AddOptions<StreamTicketValidationConfiguration>()
|
.AddOptions<StreamTicketValidationConfiguration>()
|
||||||
@@ -16,9 +18,8 @@ builder.Services.AddSingleton(serviceProvider =>
|
|||||||
|
|
||||||
builder.Services.AddSingleton<TicketValidator>();
|
builder.Services.AddSingleton<TicketValidator>();
|
||||||
|
|
||||||
const string corsPolicy = "media-clients";
|
|
||||||
var allowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>() ?? [];
|
var allowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>() ?? [];
|
||||||
builder.Services.AddCors(options => options.AddPolicy(corsPolicy, policy =>
|
builder.Services.AddCors(options => options.AddPolicy(CorsPolicy, policy =>
|
||||||
{
|
{
|
||||||
if (builder.Environment.IsDevelopment())
|
if (builder.Environment.IsDevelopment())
|
||||||
policy.AllowAnyOrigin();
|
policy.AllowAnyOrigin();
|
||||||
@@ -37,9 +38,9 @@ builder.Services
|
|||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.MapGet("/health", () => Results.Ok("healthy"))
|
app.MapGet("/health", () => Results.Ok("healthy"))
|
||||||
.WithSummary("Health Check Endpoint");
|
.WithSummary("Health check endpoint");
|
||||||
|
|
||||||
app.UseCors(corsPolicy);
|
app.UseCors(CorsPolicy);
|
||||||
app.UseMiddleware<TicketValidationMiddleware>();
|
app.UseMiddleware<TicketValidationMiddleware>();
|
||||||
app.MapReverseProxy();
|
app.MapReverseProxy();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
{
|
{
|
||||||
"StreamTicket": {
|
"StreamTicket": {
|
||||||
"PublicKeyPath": "../../../deploy/keys/stream_public.pem"
|
"PublicKeyPath": "../../../deploy/keys/stream_public.pem"
|
||||||
|
},
|
||||||
|
"ReverseProxy": {
|
||||||
|
"Routes": {
|
||||||
|
"media": {
|
||||||
|
"Transforms": [
|
||||||
|
{ "PathPattern": "/buckets/webapi-storage/{**rest}" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"ClusterId": "seaweed-filer",
|
"ClusterId": "seaweed-filer",
|
||||||
"Match": { "Path": "/media/{**rest}" },
|
"Match": { "Path": "/media/{**rest}" },
|
||||||
"Transforms": [
|
"Transforms": [
|
||||||
{ "PathPattern": "/buckets/webapi-storage/{**rest}" }
|
{ "PathPattern": "/buckets/CHANGE_ME/{**rest}" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,32 +1,27 @@
|
|||||||
using Musify.Application.Configuration;
|
using Musify.Application;
|
||||||
using Musify.Infrastructure.MassTransit;
|
using Musify.Infrastructure.MassTransit;
|
||||||
|
using Musify.Infrastructure.Observability;
|
||||||
using Musify.Infrastructure.Persistence;
|
using Musify.Infrastructure.Persistence;
|
||||||
using Musify.Infrastructure.Services;
|
using Musify.Infrastructure.Services;
|
||||||
|
|
||||||
var builder = Host.CreateApplicationBuilder();
|
var builder = Host.CreateApplicationBuilder(args);
|
||||||
|
|
||||||
builder.ConfigureContainer(new DefaultServiceProviderFactory(new ServiceProviderOptions
|
builder.ConfigureContainer(new DefaultServiceProviderFactory(new ServiceProviderOptions
|
||||||
{
|
{
|
||||||
ValidateOnBuild = false,
|
ValidateOnBuild = true,
|
||||||
ValidateScopes = true
|
ValidateScopes = true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var configuration = builder.Configuration;
|
builder.Services
|
||||||
|
.AddApplication(builder.Configuration)
|
||||||
var services = builder.Services;
|
.AddObservability(builder.Configuration)
|
||||||
|
.AddDatabase(builder.Configuration)
|
||||||
services.AddValidatedOptions<ApplicationStorageConfiguration>(configuration);
|
.AddStorageService(builder.Configuration)
|
||||||
services.AddValidatedOptions<PlayListConfiguration>(configuration);
|
.AddAudioTranscoder(builder.Configuration)
|
||||||
services.AddValidatedOptions<TrackConfiguration>(configuration);
|
.AddStreamTicketService(builder.Configuration)
|
||||||
services.AddValidatedOptions<MixConfiguration>(configuration);
|
.AddPictureService()
|
||||||
services.AddStorageService(configuration);
|
.AddMassTransitConsumers(builder.Configuration)
|
||||||
services.AddAudioTranscoder(configuration);
|
.AddJobs();
|
||||||
services.AddPictureService();
|
|
||||||
services.AddDatabase(configuration);
|
|
||||||
services.AddMassTransitConsumers(configuration);
|
|
||||||
services.AddUploadIntentConfiguration(configuration);
|
|
||||||
services.AddMediator();
|
|
||||||
services.AddJobs();
|
|
||||||
|
|
||||||
var host = builder.Build();
|
var host = builder.Build();
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,11 @@
|
|||||||
"ApplicationStorage": {
|
"ApplicationStorage": {
|
||||||
"Bucket": "webapi-storage"
|
"Bucket": "webapi-storage"
|
||||||
},
|
},
|
||||||
"AudioTranscoder": {
|
"StreamGateway": {
|
||||||
"Routes": {
|
"PublicBaseUrl": "http://localhost:8081"
|
||||||
"WorkingDirectory": "../../../deploy/.tmp"
|
},
|
||||||
}
|
"StreamTicket": {
|
||||||
|
"PrivateKeyPath": "../../../deploy/keys/stream_private.pem"
|
||||||
},
|
},
|
||||||
"Workers": {
|
"Workers": {
|
||||||
"Routes": {
|
"Routes": {
|
||||||
|
|||||||
@@ -26,11 +26,27 @@
|
|||||||
"ApplicationStorage": {
|
"ApplicationStorage": {
|
||||||
"Bucket": ""
|
"Bucket": ""
|
||||||
},
|
},
|
||||||
|
"StreamGateway": {
|
||||||
|
"PublicBaseUrl": "",
|
||||||
|
"AudioFileName": "audio.m4a"
|
||||||
|
},
|
||||||
|
"StreamTicket": {
|
||||||
|
"PrivateKeyPath": "",
|
||||||
|
"Audience": "media-gateway",
|
||||||
|
"Issuer": "musify-webapi",
|
||||||
|
"TicketTtlSeconds": 3600
|
||||||
|
},
|
||||||
|
"Playback": {
|
||||||
|
"AllowAnonymousListening": false,
|
||||||
|
"AnonymousFragmentSeconds": 0,
|
||||||
|
"EstimatedAudioBytesPerSecond": 16000
|
||||||
|
},
|
||||||
"Mix": {
|
"Mix": {
|
||||||
"ItemsPerMix": 12
|
"ItemsPerMix": 12
|
||||||
},
|
},
|
||||||
"PlayList": {
|
"PlayList": {
|
||||||
"Routes": {
|
"Routes": {
|
||||||
|
"UploadsFolder": "uploads",
|
||||||
"ParentFolder": "PlayLists",
|
"ParentFolder": "PlayLists",
|
||||||
"OriginalPicturesFolder": "OriginalPictures",
|
"OriginalPicturesFolder": "OriginalPictures",
|
||||||
"SmallPicturesFolder": "SmallPictures",
|
"SmallPicturesFolder": "SmallPictures",
|
||||||
@@ -46,8 +62,27 @@
|
|||||||
"LargeHeight": 512
|
"LargeHeight": 512
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Album": {
|
||||||
|
"Routes": {
|
||||||
|
"UploadsFolder": "uploads",
|
||||||
|
"ParentFolder": "Albums",
|
||||||
|
"OriginalPicturesFolder": "OriginalPictures",
|
||||||
|
"SmallPicturesFolder": "SmallPictures",
|
||||||
|
"MediumPicturesFolder": "MediumPictures",
|
||||||
|
"LargePicturesFolder": "LargePictures"
|
||||||
|
},
|
||||||
|
"PicturesSizes": {
|
||||||
|
"SmallWidth": 128,
|
||||||
|
"SmallHeight": 128,
|
||||||
|
"MediumWidth": 256,
|
||||||
|
"MediumHeight": 256,
|
||||||
|
"LargeWidth": 512,
|
||||||
|
"LargeHeight": 512
|
||||||
|
}
|
||||||
|
},
|
||||||
"Track": {
|
"Track": {
|
||||||
"Routes": {
|
"Routes": {
|
||||||
|
"UploadsFolder": "uploads",
|
||||||
"ParentFolder": "Tracks",
|
"ParentFolder": "Tracks",
|
||||||
"OriginalPicturesFolder": "OriginalPictures",
|
"OriginalPicturesFolder": "OriginalPictures",
|
||||||
"SmallPicturesFolder": "SmallPictures",
|
"SmallPicturesFolder": "SmallPictures",
|
||||||
|
|||||||
@@ -340,10 +340,18 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
migrate:
|
migrate:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
|
keys-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
environment:
|
environment:
|
||||||
<<: *backend-env
|
<<: *backend-env
|
||||||
DOTNET_ENVIRONMENT: ${DOTNET_ENVIRONMENT}
|
DOTNET_ENVIRONMENT: ${DOTNET_ENVIRONMENT}
|
||||||
|
# The Application handlers need these to build their dependencies; the
|
||||||
|
# Worker never issues stream tickets, so the key is never read.
|
||||||
|
StreamGateway__PublicBaseUrl: ${PUBLIC_STREAM_URL}
|
||||||
|
StreamTicket__PrivateKeyPath: /keys/stream_private.pem
|
||||||
Workers__Routes__TemporaryFilesDirectory: /tmp/musify
|
Workers__Routes__TemporaryFilesDirectory: /tmp/musify
|
||||||
|
volumes:
|
||||||
|
- ./keys:/keys:ro
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# YARP in front of the SeaweedFS filer: validates the RS256 stream ticket and
|
# YARP in front of the SeaweedFS filer: validates the RS256 stream ticket and
|
||||||
|
|||||||
Reference in New Issue
Block a user