Broker implementation updated.

This commit is contained in:
2025-07-05 08:48:14 +02:00
parent 8735510dfc
commit 91a1f7aa61
22 changed files with 587 additions and 510 deletions

View File

@ -9,9 +9,23 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerBroker.Db", "Messe
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Db", "Db", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Db", "Db", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MessengerApi", "MessengerApi", "{D520DC2F-BD81-4588-8818-D493553ACD3D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerApi.Db.Contracts", "..\subm\messengerapi\code\MessengerApi.Db.Contracts\MessengerApi.Db.Contracts.csproj", "{B75EB44A-7B25-1E85-8E93-73C1A83F91B5}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerApi.Db", "..\sub\messengerapi\code\MessengerApi.Db\MessengerApi.Db.csproj", "{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerApi.Db", "..\subm\messengerapi\code\MessengerApi.Db\MessengerApi.Db.csproj", "{9DFB9A27-503F-0FE5-9414-18A85C9F4BF4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerBroker.Configuration", "MessengerBroker.Configuration\MessengerBroker.Configuration.csproj", "{B4964B49-DB24-4A91-BD4E-2EE8009CF5B0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerApi.Configuration", "..\subm\messengerapi\code\MessengerApi.Configuration\MessengerApi.Configuration.csproj", "{2138EEC0-ADB6-4C2A-3F81-B53B3884EFBB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{929526E6-6BE1-4769-B55F-85284F774523}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerBroker.Db.Sql", "MessengerBroker.Db.Sql\MessengerBroker.Db.Sql.csproj", "{FCE9214C-CDCB-4D79-B7AB-2BCBAD41AC35}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerApi", "..\subm\messengerapi\code\MessengerApi\MessengerApi.csproj", "{36AEE097-901C-171C-BA84-C231C2EC600F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerApi.Db.Sql", "..\subm\messengerapi\code\MessengerApi.Db.Sql\MessengerApi.Db.Sql.csproj", "{5F0E1A67-056B-E6B9-1940-5F31587C05CE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerApi.Db.Npg", "..\subm\messengerapi\code\MessengerApi.Db.Npg\MessengerApi.Db.Npg.csproj", "{2A08099B-1A1E-FE40-9220-43F26AF852EC}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -27,17 +41,51 @@ Global
{52CF80F3-A938-437B-B9DD-5E64A206A641}.Debug|Any CPU.Build.0 = Debug|Any CPU {52CF80F3-A938-437B-B9DD-5E64A206A641}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52CF80F3-A938-437B-B9DD-5E64A206A641}.Release|Any CPU.ActiveCfg = Release|Any CPU {52CF80F3-A938-437B-B9DD-5E64A206A641}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52CF80F3-A938-437B-B9DD-5E64A206A641}.Release|Any CPU.Build.0 = Release|Any CPU {52CF80F3-A938-437B-B9DD-5E64A206A641}.Release|Any CPU.Build.0 = Release|Any CPU
{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B75EB44A-7B25-1E85-8E93-73C1A83F91B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34}.Debug|Any CPU.Build.0 = Debug|Any CPU {B75EB44A-7B25-1E85-8E93-73C1A83F91B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34}.Release|Any CPU.ActiveCfg = Release|Any CPU {B75EB44A-7B25-1E85-8E93-73C1A83F91B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34}.Release|Any CPU.Build.0 = Release|Any CPU {B75EB44A-7B25-1E85-8E93-73C1A83F91B5}.Release|Any CPU.Build.0 = Release|Any CPU
{9DFB9A27-503F-0FE5-9414-18A85C9F4BF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9DFB9A27-503F-0FE5-9414-18A85C9F4BF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9DFB9A27-503F-0FE5-9414-18A85C9F4BF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9DFB9A27-503F-0FE5-9414-18A85C9F4BF4}.Release|Any CPU.Build.0 = Release|Any CPU
{B4964B49-DB24-4A91-BD4E-2EE8009CF5B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4964B49-DB24-4A91-BD4E-2EE8009CF5B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4964B49-DB24-4A91-BD4E-2EE8009CF5B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4964B49-DB24-4A91-BD4E-2EE8009CF5B0}.Release|Any CPU.Build.0 = Release|Any CPU
{2138EEC0-ADB6-4C2A-3F81-B53B3884EFBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2138EEC0-ADB6-4C2A-3F81-B53B3884EFBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2138EEC0-ADB6-4C2A-3F81-B53B3884EFBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2138EEC0-ADB6-4C2A-3F81-B53B3884EFBB}.Release|Any CPU.Build.0 = Release|Any CPU
{FCE9214C-CDCB-4D79-B7AB-2BCBAD41AC35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FCE9214C-CDCB-4D79-B7AB-2BCBAD41AC35}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FCE9214C-CDCB-4D79-B7AB-2BCBAD41AC35}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FCE9214C-CDCB-4D79-B7AB-2BCBAD41AC35}.Release|Any CPU.Build.0 = Release|Any CPU
{36AEE097-901C-171C-BA84-C231C2EC600F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{36AEE097-901C-171C-BA84-C231C2EC600F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{36AEE097-901C-171C-BA84-C231C2EC600F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{36AEE097-901C-171C-BA84-C231C2EC600F}.Release|Any CPU.Build.0 = Release|Any CPU
{5F0E1A67-056B-E6B9-1940-5F31587C05CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5F0E1A67-056B-E6B9-1940-5F31587C05CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F0E1A67-056B-E6B9-1940-5F31587C05CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F0E1A67-056B-E6B9-1940-5F31587C05CE}.Release|Any CPU.Build.0 = Release|Any CPU
{2A08099B-1A1E-FE40-9220-43F26AF852EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A08099B-1A1E-FE40-9220-43F26AF852EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A08099B-1A1E-FE40-9220-43F26AF852EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A08099B-1A1E-FE40-9220-43F26AF852EC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{52CF80F3-A938-437B-B9DD-5E64A206A641} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {52CF80F3-A938-437B-B9DD-5E64A206A641} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34} = {D520DC2F-BD81-4588-8818-D493553ACD3D} {B75EB44A-7B25-1E85-8E93-73C1A83F91B5} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{9DFB9A27-503F-0FE5-9414-18A85C9F4BF4} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{B4964B49-DB24-4A91-BD4E-2EE8009CF5B0} = {929526E6-6BE1-4769-B55F-85284F774523}
{2138EEC0-ADB6-4C2A-3F81-B53B3884EFBB} = {929526E6-6BE1-4769-B55F-85284F774523}
{FCE9214C-CDCB-4D79-B7AB-2BCBAD41AC35} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{5F0E1A67-056B-E6B9-1940-5F31587C05CE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{2A08099B-1A1E-FE40-9220-43F26AF852EC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F0F93DDE-2CDC-4A7D-9D70-A7A12B3AF9CE} SolutionGuid = {F0F93DDE-2CDC-4A7D-9D70-A7A12B3AF9CE}

View File

@ -1,29 +0,0 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["MessengerBroker/MessengerBroker.csproj", "MessengerBroker/"]
RUN dotnet restore "./MessengerBroker/MessengerBroker.csproj"
COPY . .
WORKDIR "/src/MessengerBroker"
RUN dotnet build "./MessengerBroker.csproj" -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./MessengerBroker.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MessengerBroker.dll"]

View File

@ -1,22 +0,0 @@
using System.Linq.Expressions;
namespace MessengerBroker
{
public static class EntityExtensions
{
public static IQueryable<Db.Model.User> GetUsers(this IQueryable<Db.Model.User> source, Guid[] guids)
{
return source.Where(x => guids.Any(g => g == x.Id));
}
public static IQueryable<Db.Model.User> GetUsersExcept(this IQueryable<Db.Model.User> source, IEnumerable<Guid> guids)
{
return source.Where(x => !guids.Any(g => g == x.Id));
}
public static bool GetUserExists(this IQueryable<Db.Model.User> source, Guid id)
{
return source.Any(x=> x.Id == id);
}
}
}

View File

@ -0,0 +1,27 @@
using MessengerApi.Configuration.Model.Persistence;
using MessengerBroker.Configuration.Model;
using MessengerBroker.Db;
using MessengerBroker.Db.Sql;
namespace MessengerBroker.Factories
{
public class BrokerDbContextFactory
{
private readonly BrokerConfiguration configuration;
public BrokerDbContextFactory(BrokerConfiguration configuration)
{
this.configuration = configuration;
}
public BrokerDbContext CreateDbContext()
{
if(this.configuration.BrokerPersistenceConfiguration.PersistenceType == MessengerApi.Configuration.Enums.PersistenceTypes.Sql)
{
return new BrokerSqlDbContext((this.configuration.BrokerPersistenceConfiguration as SqlPersistenceConfiguration).ConnectionString);
}
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,2 @@
global using ILogger = portaloggy.ILogger;
global using portaloggy;

View File

@ -1,28 +0,0 @@
namespace MessengerBroker.Handlers
{
public class AuthHandler
{
private readonly Settings settings;
public AuthHandler(Settings settings)
{
this.settings = settings;
}
public Guid? Auth(HttpContext context)
{
var authHeader = context.Request.Headers["Authorization"].ToString();
if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer "))
{
var token = authHeader.Substring("Bearer ".Length).Trim();
if (Guid.TryParse(token, out Guid brokerId) && this.settings.Slaves.Any(x => x.BrokerId == brokerId))
{
return brokerId;
}
}
return null;
}
}
}

View File

@ -0,0 +1,78 @@
using MessengerBroker.Configuration.Model;
using MessengerBroker.Models;
using MessengerBroker.Models.Scoped;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Text.Encodings.Web;
namespace MessengerBroker.Handlers
{
/// <summary>
/// Validates our permananet API keys sent over as Bearer tokens.
/// </summary>
public class CustomBearerAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly IMemoryCache memoryCache;
private readonly BrokerConfiguration configuration;
public CustomBearerAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory loggerFactory,
UrlEncoder encoder,
IMemoryCache memoryCache,
BrokerConfiguration configuration)
: base(options, loggerFactory, encoder)
{
this.memoryCache = memoryCache;
this.configuration = configuration;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
const string HEADER = "Authorization";
const string PREFIX = "Bearer ";
if (!Request.Headers.TryGetValue(HEADER, out var authHeader) ||
!authHeader.ToString().StartsWith(PREFIX))
{
return Task.FromResult(AuthenticateResult.NoResult());
}
var token = authHeader.ToString().Substring(PREFIX.Length).Trim();
if(memoryCache.TryGetValue(token, out CachedIdentity oldCache))
{
var identity = Context.RequestServices.GetRequiredService<Identity>();
identity.Server = oldCache.Server;
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(oldCache.ClaimsPrincipal, Scheme.Name)));
}
else
{
var brokerId = Guid.Parse(token);
var server = configuration.SlaveServers.SingleOrDefault(x => x.BrokerId == brokerId);
var principal = new ClaimsPrincipal(
new ClaimsIdentity(
new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, token),
new Claim(ClaimTypes.Name, token)
}, Scheme.Name));
var cache = new CachedIdentity
{
ClaimsPrincipal = principal,
Server = server
};
memoryCache.Set(token, cache, TimeSpan.FromMinutes(5));
var identity = Context.RequestServices.GetRequiredService<Identity>();
identity.Server = server;
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(cache.ClaimsPrincipal, Scheme.Name)));
}
}
}
}

View File

@ -1,81 +0,0 @@
using MessengerApi.Db;
using MessengerBroker.Db;
using Microsoft.EntityFrameworkCore;
namespace MessengerBroker.Handlers
{
public class DataHandler
{
private readonly Settings _settings;
public Task<Tuple<MessengerApi.Db.Entities.User[], MessengerApi.Db.Entities.UserRoute[]>> GetLocalUsersAndRoutes()
{
var foreignUserIds = (Guid[])null;
using(var broCtx = new BrokerDbContext(this._settings.MessengerBrokerDbConnectionString))
{
foreignUserIds = broCtx.Users.Select(x => x.Id).ToArray();
}
using (var apiCtx = new MessengerDbContext(this._settings.MessengerApiDbConnectionString))
{
var localUsers = apiCtx.Users
.Where(x => !foreignUserIds.Any(f => f == x.Id))
.ToArray();
var localRoutes = apiCtx.UserRoutes
.Include(x => x.From)
.Include(x => x.To)
.Where(x => localUsers.Any(l => l.Id == x.From.Id) && localUsers.Any(l => l.Id == x.To.Id))
.ToArray();
return Task.FromResult(new Tuple<MessengerApi.Db.Entities.User[], MessengerApi.Db.Entities.UserRoute[]>(
localUsers,
localRoutes));
}
}
private Task<MessengerApi.Db.Entities.User[]> GetForeignUsers(Guid brokerId)
{
var foreignUserIds = (Guid[])null;
using (var broCtx = new BrokerDbContext(this._settings.MessengerBrokerDbConnectionString))
{
foreignUserIds = broCtx.Users.Where(x=>x.BrokerId == brokerId).Select(x => x.Id).ToArray();
}
using (var apiCtx = new MessengerDbContext(this._settings.MessengerApiDbConnectionString))
{
var localUsers = apiCtx.Users
.Where(x => !foreignUserIds.Any(f => f == x.Id))
.ToArray();
return Task.FromResult(localUsers);
}
}
public async Task<MessengerApi.Db.Entities.Message[]> GetMessages(Guid brokerId, DateTime sinceUtc)
{
var userIds = (Guid[])null;
if(brokerId == this._settings.BrokerId)
{
// Our messages.
var users = await this.GetLocalUsersAndRoutes();
userIds = users.Item1.Select(x => x.Id).ToArray();
}
else
{
var users = await this.GetForeignUsers(brokerId);
userIds = users.Select(x => x.Id).ToArray();
}
using (var apiCtx = new MessengerDbContext(this._settings.MessengerApiDbConnectionString))
{
var messages = apiCtx.Messages
.Include(x => x.From).Include(x => x.To)
.Where(x => x.CreatedUtc >= sinceUtc && userIds.Contains(x.From.Id))
.ToArray();
return messages;
}
}
}
}

View File

@ -0,0 +1,49 @@
using MessengerApi.Factories;
using MessengerBroker.Configuration.Model;
using MessengerBroker.Factories;
using MessengerBroker.Model.Http;
using Microsoft.EntityFrameworkCore;
namespace MessengerBroker.Handlers.Endpoint
{
public class MessagesEndpointHandler
{
private readonly BrokerConfiguration configuration;
private readonly BrokerDbContextFactory brokerDbContextFactory;
private readonly DbContextFactory messengerDbContextFactory;
public MessagesEndpointHandler(
BrokerConfiguration configuration,
BrokerDbContextFactory brokerDbContextFactory,
DbContextFactory messengerDbContextFactory)
{
this.configuration = configuration;
this.brokerDbContextFactory = brokerDbContextFactory;
this.messengerDbContextFactory = messengerDbContextFactory;
}
public async Task<Messages.MessagesResponse> GetMessages(Messages.MessagesRequest request)
{
using var broCtx = this.brokerDbContextFactory.CreateDbContext();
var brokerMessageIdCollection = broCtx.Messages
.Where(x => x.BrokerId == request.OwnerBrokerId)
.Select(x => x.Id)
.Take(1000)
.ToArray();
using var apiCtx = this.messengerDbContextFactory.CreateDbContext();
var messages = apiCtx.Messages
.Where(x => x.CreatedUtc >= request.SinceUtc && brokerMessageIdCollection.Contains(x.Id))
.ToArray();
var response = new Messages.MessagesResponse
{
Messages = messages
};
return response;
}
}
}

View File

@ -0,0 +1,65 @@
using MessengerApi.Factories;
using MessengerBroker.Configuration.Model;
using MessengerBroker.Factories;
using MessengerBroker.Model.Http;
using Microsoft.EntityFrameworkCore;
namespace MessengerBroker.Handlers.Endpoint
{
public class UsersEndpointHandler
{
private readonly BrokerConfiguration configuration;
private readonly BrokerDbContextFactory brokerDbContextFactory;
private readonly DbContextFactory messengerDbContextFactory;
public UsersEndpointHandler(
BrokerConfiguration configuration,
BrokerDbContextFactory brokerDbContextFactory,
DbContextFactory messengerDbContextFactory)
{
this.configuration = configuration;
this.brokerDbContextFactory = brokerDbContextFactory;
this.messengerDbContextFactory = messengerDbContextFactory;
}
public Task<Users.UsersResponse> GetUsers()
{
var foreignUserIds = (Guid[])null;
using(var broCtx = this.brokerDbContextFactory.CreateDbContext())
{
foreignUserIds = broCtx.Users.Select(x => x.Id).ToArray();
}
using (var apiCtx = this.messengerDbContextFactory.CreateDbContext())
{
var localUsers = apiCtx.Users
.Where(x => !foreignUserIds.Any(f => f == x.Id))
.ToArray();
var localRoutes = apiCtx.UserRoutes
.Include(x => x.From)
.Include(x => x.To)
.Where(x => localUsers.Any(l => l.Id == x.From.Id) && localUsers.Any(l => l.Id == x.To.Id))
.ToArray();
return Task.FromResult(new Users.UsersResponse
{
Users = localUsers.Select(x => new Users.UsersResponse.User
{
Id = x.Id,
ApiKey = x.ApiKey,
IsEnabled = x.IsEnabled,
Name = x.Name
}).ToArray(),
UserRoutes = localRoutes.Select(x => new Users.UsersResponse.UserRoute
{
Id = x.Id,
FromId = x.From.Id,
ToId = x.To.Id
}).ToArray()
});
}
}
}
}

View File

@ -1,196 +1,196 @@
using MessengerApi.Db; //using MessengerApi.Db;
using MessengerBroker.Db; //using MessengerBroker.Db;
using Microsoft.EntityFrameworkCore; //using Microsoft.EntityFrameworkCore;
using System.Text.Json; //using System.Text.Json;
namespace MessengerBroker.Handlers //namespace MessengerBroker.Handlers
{ //{
public class MasterHandler // public class MasterHandler
{ // {
private readonly Settings settings; // private readonly Settings settings;
private readonly HttpClient httpClient = new HttpClient(); // private readonly HttpClient httpClient = new HttpClient();
public async Task BeginSyncingWithMaster(Settings.MasterServer masterServer, CancellationToken ct = default) // public async Task BeginSyncingWithMaster(Settings.MasterServer masterServer, CancellationToken ct = default)
{ // {
while (!ct.IsCancellationRequested) // while (!ct.IsCancellationRequested)
{ // {
await Task.Delay(TimeSpan.FromSeconds(1)); // await Task.Delay(TimeSpan.FromSeconds(1));
var usersRequest = new HttpRequestMessage(HttpMethod.Get, $"{masterServer.BrokerApiUrl}/users"); // var usersRequest = new HttpRequestMessage(HttpMethod.Get, $"{masterServer.BrokerApiUrl}/users");
usersRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", this.settings.BrokerId.ToString()); // usersRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", this.settings.BrokerId.ToString());
var usersResponseMessage = await this.httpClient.SendAsync(usersRequest, ct); // var usersResponseMessage = await this.httpClient.SendAsync(usersRequest, ct);
if (!usersResponseMessage.IsSuccessStatusCode) // if (!usersResponseMessage.IsSuccessStatusCode)
{ // {
continue; // continue;
} // }
var usersResponse = JsonSerializer.Deserialize<Model.Http.Users.UsersResponse>(await usersResponseMessage.Content.ReadAsStringAsync()); // var usersResponse = JsonSerializer.Deserialize<Model.Http.Users.UsersResponse>(await usersResponseMessage.Content.ReadAsStringAsync());
using (var broCtx = new BrokerDbContext(this.settings.MessengerBrokerDbConnectionString)) // using (var broCtx = new BrokerDbContext(this.settings.MessengerBrokerDbConnectionString))
using (var apiCtx = new MessengerDbContext(this.settings.MessengerApiDbConnectionString)) // using (var apiCtx = new MessengerDbContext(this.settings.MessengerApiDbConnectionString))
{ // {
var updatedUsers = usersResponse.Users.Where(x => broCtx.Users.GetUserExists(x.Id)).ToList(); // var updatedUsers = usersResponse.Users.Where(x => broCtx.Users.GetUserExists(x.Id)).ToList();
updatedUsers.ForEach(async x => await this.UpdateUser(x, apiCtx)); // updatedUsers.ForEach(async x => await this.UpdateUser(x, apiCtx));
var deletedUsers = broCtx.Users.GetUsersExcept(usersResponse.Users.Select(x => x.Id)).ToList(); // var deletedUsers = broCtx.Users.GetUsersExcept(usersResponse.Users.Select(x => x.Id)).ToList();
deletedUsers.ForEach(async x => await this.RemoveUser(x.Id, broCtx, apiCtx)); // deletedUsers.ForEach(async x => await this.RemoveUser(x.Id, broCtx, apiCtx));
var addedUsers = usersResponse.Users.Where(x => !broCtx.Users.GetUserExists(x.Id)).ToList(); // var addedUsers = usersResponse.Users.Where(x => !broCtx.Users.GetUserExists(x.Id)).ToList();
addedUsers.ForEach(async x => await this.AddUser(x, masterServer.BrokerId, broCtx, apiCtx)); // addedUsers.ForEach(async x => await this.AddUser(x, masterServer.BrokerId, broCtx, apiCtx));
foreach (var route in usersResponse.UserRoutes // foreach (var route in usersResponse.UserRoutes
.Where(r => apiCtx.UserRoutes // .Where(r => apiCtx.UserRoutes
.Include(x => x.From) // .Include(x => x.From)
.Include(x => x.To) // .Include(x => x.To)
.Any(apir => apir.Id == r.Id && (apir.From.Id != r.FromId || apir.To.Id != r.ToId)))) // .Any(apir => apir.Id == r.Id && (apir.From.Id != r.FromId || apir.To.Id != r.ToId))))
{ // {
var existing = apiCtx.UserRoutes.Include(x => x.From).Include(x => x.To).Single(x => x.Id == route.Id); // var existing = apiCtx.UserRoutes.Include(x => x.From).Include(x => x.To).Single(x => x.Id == route.Id);
existing.From = apiCtx.Users.Single(x => x.Id == route.FromId); // existing.From = apiCtx.Users.Single(x => x.Id == route.FromId);
existing.To = apiCtx.Users.Single(x => x.Id == route.ToId); // existing.To = apiCtx.Users.Single(x => x.Id == route.ToId);
} // }
foreach (var deletedRoute in apiCtx.UserRoutes // foreach (var deletedRoute in apiCtx.UserRoutes
.Where(x => !usersResponse.UserRoutes.Any(r => r.Id == x.Id))) // .Where(x => !usersResponse.UserRoutes.Any(r => r.Id == x.Id)))
{ // {
apiCtx.UserRoutes.Remove(deletedRoute); // apiCtx.UserRoutes.Remove(deletedRoute);
} // }
foreach (var addedRoute in usersResponse.UserRoutes // foreach (var addedRoute in usersResponse.UserRoutes
.Where(r => !apiCtx.UserRoutes.Any(x => x.Id == r.Id))) // .Where(r => !apiCtx.UserRoutes.Any(x => x.Id == r.Id)))
{ // {
apiCtx.UserRoutes.Add(new MessengerApi.Db.Entities.UserRoute // apiCtx.UserRoutes.Add(new MessengerApi.Db.Entities.UserRoute
{ // {
Id = addedRoute.Id, // Id = addedRoute.Id,
From = apiCtx.Users.Single(x => x.Id == addedRoute.FromId), // From = apiCtx.Users.Single(x => x.Id == addedRoute.FromId),
To = apiCtx.Users.Single(x => x.Id == addedRoute.ToId) // To = apiCtx.Users.Single(x => x.Id == addedRoute.ToId)
}); // });
} // }
broCtx.SaveChanges(); // broCtx.SaveChanges();
apiCtx.SaveChanges(); // apiCtx.SaveChanges();
} // }
var messagesRequest = new HttpRequestMessage(HttpMethod.Get, $"{masterServer.BrokerApiUrl}/messages"); // var messagesRequest = new HttpRequestMessage(HttpMethod.Get, $"{masterServer.BrokerApiUrl}/messages");
messagesRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", this.settings.BrokerId.ToString()); // messagesRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", this.settings.BrokerId.ToString());
var messagesResponseMessage = await this.httpClient.SendAsync(messagesRequest, ct); // var messagesResponseMessage = await this.httpClient.SendAsync(messagesRequest, ct);
if (!messagesResponseMessage.IsSuccessStatusCode) // if (!messagesResponseMessage.IsSuccessStatusCode)
{ // {
continue; // continue;
} // }
var messagesResponse = JsonSerializer.Deserialize<Model.Http.Sync.SyncResponse>(await messagesResponseMessage.Content.ReadAsStringAsync()); // var messagesResponse = JsonSerializer.Deserialize<Model.Http.Messages.MessagesResponse>(await messagesResponseMessage.Content.ReadAsStringAsync());
using (var broCtx = new BrokerDbContext(this.settings.MessengerBrokerDbConnectionString)) // using (var broCtx = new BrokerDbContext(this.settings.MessengerBrokerDbConnectionString))
using (var apiCtx = new MessengerDbContext(this.settings.MessengerApiDbConnectionString)) // using (var apiCtx = new MessengerDbContext(this.settings.MessengerApiDbConnectionString))
{ // {
var addedMessages = messagesResponse.Messages // var addedMessages = messagesResponse.Messages
.Where(m => broCtx.Messages.Any(x => x.BrokerId == masterServer.BrokerId && x.Id == m.Id) == false) // .Where(m => broCtx.Messages.Any(x => x.BrokerId == masterServer.BrokerId && x.Id == m.Id) == false)
.ToList(); // .ToList();
addedMessages.ForEach(x => // addedMessages.ForEach(x =>
{ // {
broCtx.Messages.Add(new Db.Model.Message // broCtx.Messages.Add(new Db.Model.Message
{ // {
BrokerId = masterServer.BrokerId, // BrokerId = masterServer.BrokerId,
Id = x.Id // Id = x.Id
}); // });
apiCtx.Messages.Add(new MessengerApi.Db.Entities.Message // apiCtx.Messages.Add(new MessengerApi.Db.Entities.Message
{ // {
Id = x.Id, // Id = x.Id,
CreatedUtc = x.CreatedUtc, // CreatedUtc = x.CreatedUtc,
From = x.From != null ? (apiCtx.Users.SingleOrDefault(u => u.Id == x.From.Value)) ?? null : null, // From = x.From != null ? (apiCtx.Users.SingleOrDefault(u => u.Id == x.From.Value)) ?? null : null,
To = x.To != null ? (apiCtx.Users.SingleOrDefault(u => u.Id == x.To.Value)) ?? null : null, // To = x.To != null ? (apiCtx.Users.SingleOrDefault(u => u.Id == x.To.Value)) ?? null : null,
IsAcknowledged = x.IsAcknowledged, // IsAcknowledged = x.IsAcknowledged,
IsDelivered = x.IsDelivered, // IsDelivered = x.IsDelivered,
Payload = x.Payload, // Payload = x.Payload,
PayloadId = x.PayloadId, // PayloadId = x.PayloadId,
PayloadLifespanInSeconds = x.PayloadLifespanInSeconds, // PayloadLifespanInSeconds = x.PayloadLifespanInSeconds,
PayloadTimestamp = x.PayloadTimestamp, // PayloadTimestamp = x.PayloadTimestamp,
PayloadType = x.PayloadType, // PayloadType = x.PayloadType,
}); // });
}); // });
var existingMessages = messagesResponse.Messages // var existingMessages = messagesResponse.Messages
.Except(addedMessages) // .Except(addedMessages)
.ToList(); // .ToList();
existingMessages.ForEach(x => // existingMessages.ForEach(x =>
{ // {
var existing = apiCtx.Messages.SingleOrDefault(a => a.Id == x.Id); // var existing = apiCtx.Messages.SingleOrDefault(a => a.Id == x.Id);
if(existing != null && (existing.IsDelivered != x.IsDelivered || existing.IsAcknowledged != x.IsAcknowledged)) // if(existing != null && (existing.IsDelivered != x.IsDelivered || existing.IsAcknowledged != x.IsAcknowledged))
{ // {
existing.IsDelivered = x.IsDelivered; // existing.IsDelivered = x.IsDelivered;
existing.IsAcknowledged = x.IsAcknowledged; // existing.IsAcknowledged = x.IsAcknowledged;
} // }
}); // });
broCtx.SaveChanges(); // broCtx.SaveChanges();
apiCtx.SaveChanges(); // apiCtx.SaveChanges();
} // }
} // }
} // }
private async Task RemoveUser(Guid id, BrokerDbContext broCtx, MessengerDbContext apiCtx) // private async Task RemoveUser(Guid id, BrokerDbContext broCtx, MessengerDbContext apiCtx)
{ // {
var broUser = await broCtx.Users.SingleOrDefaultAsync(x => x.Id == id); // var broUser = await broCtx.Users.SingleOrDefaultAsync(x => x.Id == id);
if (broUser != null) // if (broUser != null)
{ // {
broCtx.Users.Remove(broUser); // broCtx.Users.Remove(broUser);
} // }
var apiUser = await apiCtx.Users.SingleOrDefaultAsync(x => x.Id == id); // var apiUser = await apiCtx.Users.SingleOrDefaultAsync(x => x.Id == id);
if (apiUser != null) // if (apiUser != null)
{ // {
apiCtx.Users.Remove(apiUser); // apiCtx.Users.Remove(apiUser);
} // }
} // }
private async Task AddUser(Model.Http.Users.UsersResponse.User user, Guid brokerId, BrokerDbContext broCtx, MessengerDbContext apiCtx) // private async Task AddUser(Model.Http.Users.UsersResponse.User user, Guid brokerId, BrokerDbContext broCtx, MessengerDbContext apiCtx)
{ // {
if (broCtx.Users.GetUserExists(user.Id) == false) // if (broCtx.Users.GetUserExists(user.Id) == false)
{ // {
broCtx.Users.Add(new Db.Model.User // broCtx.Users.Add(new Db.Model.User
{ // {
Id = user.Id, // Id = user.Id,
BrokerId = brokerId // BrokerId = brokerId
}); // });
} // }
if (apiCtx.Users.Any(x => x.Id == user.Id)) // if (apiCtx.Users.Any(x => x.Id == user.Id))
{ // {
await this.UpdateUser(user, apiCtx); // await this.UpdateUser(user, apiCtx);
} // }
else // else
{ // {
await apiCtx.Users.AddAsync(new MessengerApi.Db.Entities.User // await apiCtx.Users.AddAsync(new MessengerApi.Db.Entities.User
{ // {
Id = user.Id, // Id = user.Id,
ApiKey = user.ApiKey, // ApiKey = user.ApiKey,
CanReceive = user.CanReceive, // CanReceive = user.CanReceive,
CanSend = user.CanSend, // CanSend = user.CanSend,
IsEnabled = user.IsEnabled, // IsEnabled = user.IsEnabled,
Name = user.Name // Name = user.Name
}); // });
} // }
} // }
private async Task UpdateUser(Model.Http.Users.UsersResponse.User user, MessengerDbContext apiCtx) // private async Task UpdateUser(Model.Http.Users.UsersResponse.User user, MessengerDbContext apiCtx)
{ // {
var apiUser = await apiCtx.Users.SingleAsync(x => x.Id == user.Id); // var apiUser = await apiCtx.Users.SingleAsync(x => x.Id == user.Id);
apiUser.ApiKey = user.ApiKey; // apiUser.ApiKey = user.ApiKey;
apiUser.CanReceive = user.CanReceive; // apiUser.CanReceive = user.CanReceive;
apiUser.CanSend = user.CanSend; // apiUser.CanSend = user.CanSend;
apiUser.IsEnabled = user.IsEnabled; // apiUser.IsEnabled = user.IsEnabled;
apiUser.Name = user.Name; // apiUser.Name = user.Name;
} // }
} // }
} //}

View File

@ -12,7 +12,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\sub\messengerapi\code\MessengerApi.Db\MessengerApi.Db.csproj" /> <ProjectReference Include="..\..\subm\messengerapi\code\MessengerApi\MessengerApi.csproj" />
<ProjectReference Include="..\MessengerBroker.Configuration\MessengerBroker.Configuration.csproj" />
<ProjectReference Include="..\MessengerBroker.Db.Sql\MessengerBroker.Db.Sql.csproj" />
<ProjectReference Include="..\MessengerBroker.Db\MessengerBroker.Db.csproj" /> <ProjectReference Include="..\MessengerBroker.Db\MessengerBroker.Db.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -1,6 +0,0 @@
@MessengerBroker_HostAddress = http://localhost:5048
GET {{MessengerBroker_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -1,42 +0,0 @@
namespace MessengerBroker.Model.Http
{
public class Sync
{
public class SyncRequest
{
public Guid BrokerId { get; set; }
public DateTime SinceUtc { get; set; }
}
public class SyncResponse
{
public Message[] Messages { get; set; }
public class Message
{
public Guid Id { get; set; }
public DateTime CreatedUtc { get; set; }
public Guid? From { get; set; }
public Guid? To { get; set; }
public bool IsDelivered { get; set; }
public bool IsAcknowledged { get; set; }
public string PayloadId { get; set; }
public string PayloadType { get; set; }
public string Payload { get; set; }
public DateTime? PayloadTimestamp { get; set; }
public int? PayloadLifespanInSeconds { get; set; }
}
}
}
}

View File

@ -0,0 +1,12 @@
using MessengerBroker.Configuration.Model.Servers;
using System.Security.Claims;
namespace MessengerBroker.Models
{
public class CachedIdentity
{
public SlaveServer Server { get; set; }
public ClaimsPrincipal ClaimsPrincipal { get; set; }
}
}

View File

@ -0,0 +1,17 @@
namespace MessengerBroker.Model.Http
{
public class Messages
{
public class MessagesRequest
{
public Guid OwnerBrokerId { get; set; }
public DateTime SinceUtc { get; set; }
}
public class MessagesResponse
{
public MessengerApi.Db.Entities.Message[] Messages { get; set; }
}
}
}

View File

@ -17,10 +17,6 @@
public string Name { get; set; } public string Name { get; set; }
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
public bool CanSend { get; set; }
public bool CanReceive { get; set; }
} }
public class UserRoute public class UserRoute

View File

@ -0,0 +1,9 @@
using MessengerBroker.Configuration.Model.Servers;
namespace MessengerBroker.Models.Scoped
{
public class Identity
{
public SlaveServer Server { get; set; }
}
}

View File

@ -1,6 +1,14 @@
using MessengerApi.Configuration.Sources.Environment;
using MessengerBroker.Configuration.Model;
using MessengerBroker.Factories;
using MessengerBroker.Handlers; using MessengerBroker.Handlers;
using MessengerBroker.Handlers.Endpoint;
using MessengerBroker.Model.Http; using MessengerBroker.Model.Http;
using System.Runtime.CompilerServices; using MessengerBroker.Models.Scoped;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.EntityFrameworkCore;
using System.Net;
namespace MessengerBroker namespace MessengerBroker
{ {
@ -8,94 +16,119 @@ namespace MessengerBroker
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
var settings = (Settings)null; BrokerConfiguration configuration = null;
try
{
configuration = new BrokerConfiguration(new EnvironmentConfigurationSource());
}
catch (Exception ex)
{
Console.WriteLine("Can't load settings.", ex);
throw;
}
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<ILogger, ConsoleLogger>();
builder.Services.AddSingleton<BrokerConfiguration>(configuration);
builder.Services.AddSingleton<BrokerDbContextFactory>();
builder.Services.AddSingleton<MessengerApi.Factories.DbContextFactory>((sp) =>
{
return new MessengerApi.Factories.DbContextFactory(configuration.ApiPersistenceConfiguration);
});
builder.Services.AddScoped<Identity>();
builder.Services.AddScoped<UsersEndpointHandler>();
builder.Services.AddScoped<MessagesEndpointHandler>();
// Authentication.
builder.Services
.AddAuthentication("Bearer")
.AddScheme<AuthenticationSchemeOptions, CustomBearerAuthenticationHandler>("Bearer", null);
// Proxy registration to forward real client IPs.
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
foreach (var proxy in configuration.Proxies)
{
options.KnownProxies.Add(IPAddress.Parse(proxy));
}
});
var app = builder.Build(); var app = builder.Build();
app.MapGet("/users", async (HttpContext httpContext) => // DB Migrations
using (var ctx = app.Services.GetRequiredService<BrokerDbContextFactory>().CreateDbContext())
{ {
var authHandler = app.Services.GetService<AuthHandler>(); var migrationLogger = app.Services.GetRequiredService<ILogger>();
var brokerId = authHandler.Auth(httpContext);
if(brokerId == null) try
{ {
return Results.Unauthorized(); if (ctx.Database.GetPendingMigrations().Any())
{
migrationLogger.Info("Applying migrations.");
ctx.Database.Migrate();
}
else
{
migrationLogger.Info("No migrations pending.");
}
} }
catch (Exception ex)
var dataHandler = app.Services.GetService<DataHandler>();
var usersAndRoutes = await dataHandler.GetLocalUsersAndRoutes();
var response = new Users.UsersResponse
{ {
Users = usersAndRoutes.Item1.Select(x => new Users.UsersResponse.User migrationLogger.Error("Can't run migrations successfully.", ex);
{ throw;
Id = x.Id, }
Name = x.Name, }
ApiKey = x.ApiKey,
CanReceive = x.CanReceive,
CanSend = x.CanSend,
IsEnabled = x.IsEnabled,
}).ToArray(),
UserRoutes = usersAndRoutes.Item2.Select(x => new Users.UsersResponse.UserRoute
{
Id = x.Id,
FromId = x.From.Id,
ToId = x.To.Id
}).ToArray()
};
//// Housekeeping.
//if (configuration.HousekeepingEnabled)
//{
// _ = Task.Run(async () =>
// {
// while (true)
// {
// await app.Services.GetService<HousekeepingHandler>().RemoveOldMessages();
// await Task.Delay(TimeSpan.FromMinutes(1));
// }
// });
//}
//// Run pull sync from masters.
//_ = Task.Run(async () =>
//{
// var cts = new CancellationTokenSource();
// var handler = app.Services.GetService<MasterHandler>();
// foreach (var master in settings.Masters)
// {
// _ = handler.BeginSyncingWithMaster(master, cts.Token);
// }
//});
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseForwardedHeaders();
app.MapGet("/users", async (UsersEndpointHandler handler) =>
{
var response = await handler.GetUsers();
return Results.Json(response); return Results.Json(response);
}); });
app.MapGet("/sync", async (HttpContext httpContext, [AsParameters] Sync.SyncRequest request) => app.MapGet("/messages", async (
MessagesEndpointHandler handler,
[AsParameters] Messages.MessagesRequest request) =>
{ {
var authHandler = app.Services.GetService<AuthHandler>(); var response = await handler.GetMessages(request);
var brokerId = authHandler.Auth(httpContext);
if(brokerId == null)
{
return Results.Unauthorized();
}
else if(request.BrokerId != brokerId && request.BrokerId != settings.BrokerId)
{
return Results.Unauthorized();
}
var dataHandler = app.Services.GetService<DataHandler>();
var messages = await dataHandler.GetMessages(request.BrokerId, request.SinceUtc);
var response = new Sync.SyncResponse
{
Messages = messages.Select(x => new Sync.SyncResponse.Message
{
Id = x.Id,
CreatedUtc = x.CreatedUtc,
From = x.From.Id,
To = x.To.Id,
IsAcknowledged = x.IsAcknowledged,
IsDelivered = x.IsDelivered,
Payload = x.Payload,
PayloadId = x.PayloadId,
PayloadLifespanInSeconds = x.PayloadLifespanInSeconds,
PayloadTimestamp = x.PayloadTimestamp,
PayloadType = x.PayloadType
}).ToArray()
};
return Results.Json(response); return Results.Json(response);
}); });
_ = Task.Run(async () =>
{
var cts = new CancellationTokenSource();
var handler = app.Services.GetService<MasterHandler>();
foreach(var master in settings.Masters)
{
_ = handler.BeginSyncingWithMaster(master, cts.Token);
}
});
app.Run(); app.Run();
} }
} }

View File

@ -1,36 +0,0 @@
namespace MessengerBroker
{
public class Settings
{
/// <summary>
/// Connection string to Messenger API DB.
/// </summary>
public string MessengerApiDbConnectionString { get; set; }
public string MessengerBrokerDbConnectionString { get; set; }
public Guid BrokerId { get; set; }
public MasterServer[] Masters { get; set; }
public SlaveServer[] Slaves { get; set; }
/// <summary>
/// A server that we are a slave to. If this server goes down, their users will alternate to us and we have to provide service during outage. We pull data from this server.
/// </summary>
public class MasterServer
{
public string BrokerApiUrl { get; set; }
public Guid BrokerId { set; get; }
}
/// <summary>
/// A server that slaves to us in case of our own outage. They pull from us.
/// </summary>
public class SlaveServer
{
public Guid BrokerId { get; set; }
}
}
}

View File

@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}