reworking Program.cs

This commit is contained in:
2022-06-14 15:20:13 -05:00
parent db867eb428
commit 96cfd2411b
12 changed files with 282 additions and 356 deletions

View File

@@ -0,0 +1,18 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "6.0.5",
"commands": [
"dotnet-ef"
]
},
"microsoft.dotnet-msidentity": {
"version": "1.0.3",
"commands": [
"dotnet-msidentity"
]
}
}
}

View File

@@ -5,17 +5,20 @@
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>5</AnalysisLevel>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
<SignAssembly>False</SignAssembly>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="10.3.6" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.1" />
<PackageReference Include="Microsoft.Identity.Web" Version="1.21.1" />
<PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="1.21.1" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="1.16.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NSwag.AspNetCore" Version="13.15.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,20 +1,214 @@
using FluentValidation;
using Gameboard.ShogiUI.Sockets.Managers;
using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
using Gameboard.ShogiUI.Sockets.Repositories;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
using Gameboard.ShogiUI.Sockets.Services;
using Gameboard.ShogiUI.Sockets.Services.RequestValidators;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpLogging;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
using System.Text;
namespace Gameboard.ShogiUI.Sockets
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
ConfigureAuthentication(builder);
ConfigureControllersWithNewtonsoft(builder);
ConfigureSwagger(builder);
ConfigureDependencyInjection(builder);
ConfigureLogging(builder);
var app = builder.Build();
app.UseHttpLogging();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.OAuthConfigObject.ClientId = builder.Configuration["AzureAd:ClientId"];
options.OAuthConfigObject.UsePkceWithAuthorizationCodeGrant = true;
});
app.UseHttpsRedirection(); // Apache handles HTTPS in production.
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
UseCorsAndWebSockets(app);
app.Run();
}
private static void UseCorsAndWebSockets(WebApplication app)
{
// TODO: Figure out how to make a middleware for sockets?
var socketService = app.Services.GetRequiredService<ISocketService>();
var origins = new[] {
"http://localhost:3000", "https://localhost:3000",
"http://127.0.0.1:3000", "https://127.0.0.1:3000",
"https://api.lucaserver.space", "https://lucaserver.space"
};
var socketOptions = new WebSocketOptions();
foreach (var o in origins)
socketOptions.AllowedOrigins.Add(o);
app.UseCors(opt => opt.WithOrigins(origins).AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("Set-Cookie").AllowCredentials());
app.UseWebSockets(socketOptions);
app.Use(async (context, next) =>
{
Console.WriteLine("Use websocket");
if (context.WebSockets.IsWebSocketRequest)
{
Console.WriteLine("Is websocket request");
await socketService.HandleSocketRequest(context);
}
else
{
await next();
}
});
}
private static void ConfigureLogging(WebApplicationBuilder builder)
{
builder.Services.AddHttpLogging(options =>
{
options.LoggingFields = HttpLoggingFields.Request;
});
}
private static void ConfigureAuthentication(WebApplicationBuilder builder)
{
// Add services to the container.
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services
.AddAuthentication("CookieOrJwt")
.AddPolicyScheme("CookieOrJwt", "Either cookie or jwt", options =>
{
options.ForwardDefaultSelector = context =>
{
var bearerAuth = context.Request.Headers["Authorization"].FirstOrDefault()?.StartsWith("Bearer ") ?? false;
return bearerAuth
? JwtBearerDefaults.AuthenticationScheme
: CookieAuthenticationDefaults.AuthenticationScheme;
};
})
.AddCookie(options =>
{
options.Cookie.Name = "session-id";
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.SlidingExpiration = true;
});
}
private static void ConfigureControllersWithNewtonsoft(WebApplicationBuilder builder)
{
builder.Services
.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.Formatting = Formatting.Indented;
options.SerializerSettings.ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true }
};
options.SerializerSettings.Converters = new[] { new StringEnumConverter() };
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
});
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = true
}
},
Converters = new[] { new StringEnumConverter() },
NullValueHandling = NullValueHandling.Ignore,
};
}
private static void ConfigureDependencyInjection(WebApplicationBuilder builder)
{
var services = builder.Services;
services.AddSingleton<IJoinByCodeHandler, JoinByCodeHandler>();
services.AddSingleton<ISocketConnectionManager, SocketConnectionManager>();
services.AddSingleton<ISocketTokenCache, SocketTokenCache>();
services.AddSingleton<IGameboardManager, GameboardManager>();
services.AddSingleton<IValidator<JoinByCodeRequest>, JoinByCodeRequestValidator>();
services.AddSingleton<IValidator<JoinGameRequest>, JoinGameRequestValidator>();
services.AddSingleton<ISocketService, SocketService>();
services.AddTransient<IGameboardRepository, GameboardRepository>();
services.AddSingleton<IClaimsTransformation, ShogiUserClaimsTransformer>();
services.AddHttpClient("couchdb", c =>
{
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("admin:admin"));
c.DefaultRequestHeaders.Add("Accept", "application/json");
c.DefaultRequestHeaders.Add("Authorization", $"Basic {base64}");
var baseUrl = $"{builder.Configuration["AppSettings:CouchDB:Url"]}/{builder.Configuration["AppSettings:CouchDB:Database"]}/";
c.BaseAddress = new Uri(baseUrl);
});
services.AddTransient<IModelMapper, ModelMapper>();
}
private static void ConfigureSwagger(WebApplicationBuilder builder)
{
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
var bearerKey = "Bearer";
options.AddSecurityDefinition(bearerKey, new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri("https://login.microsoftonline.com/common/oauth2/v2.0/authorize"),
TokenUrl = new Uri("https://login.microsoftonline.com/common/oauth2/v2.0/token"),
}
},
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
});
// This adds the lock symbol next to every route in SwaggerUI.
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme{ Reference = new OpenApiReference{ Type = ReferenceType.SecurityScheme, Id = bearerKey } },
Array.Empty<string>()
}
});
});
}
}
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<DeleteExistingFiles>true</DeleteExistingFiles>
<ExcludeApp_Data>false</ExcludeApp_Data>
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net6.0\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>net6.0</TargetFramework>
<ProjectGuid>4ff35f9d-e525-46cf-a8a6-a147fe50ad68</ProjectGuid>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>

View File

@@ -3,7 +3,7 @@
"Kestrel": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "/swagger",
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},

View File

@@ -0,0 +1,8 @@
{
"dependencies": {
"identityapp1": {
"type": "identityapp",
"dynamicId": null
}
}
}

View File

@@ -0,0 +1,8 @@
{
"dependencies": {
"identityapp1": {
"type": "identityapp.default",
"dynamicId": null
}
}
}

View File

@@ -1,210 +0,0 @@
using FluentValidation;
using Gameboard.ShogiUI.Sockets.Managers;
using Gameboard.ShogiUI.Sockets.Managers.ClientActionHandlers;
using Gameboard.ShogiUI.Sockets.Repositories;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
using Gameboard.ShogiUI.Sockets.Services;
using Gameboard.ShogiUI.Sockets.Services.RequestValidators;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Gameboard.ShogiUI.Sockets
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IJoinByCodeHandler, JoinByCodeHandler>();
services.AddSingleton<ISocketConnectionManager, SocketConnectionManager>();
services.AddSingleton<ISocketTokenCache, SocketTokenCache>();
services.AddSingleton<IGameboardManager, GameboardManager>();
services.AddSingleton<IValidator<JoinByCodeRequest>, JoinByCodeRequestValidator>();
services.AddSingleton<IValidator<JoinGameRequest>, JoinGameRequestValidator>();
services.AddSingleton<ISocketService, SocketService>();
services.AddTransient<IGameboardRepository, GameboardRepository>();
services.AddSingleton<IClaimsTransformation, ShogiUserClaimsTransformer>();
services.AddHttpClient("couchdb", c =>
{
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("admin:admin"));
c.DefaultRequestHeaders.Add("Accept", "application/json");
c.DefaultRequestHeaders.Add("Authorization", $"Basic {base64}");
var baseUrl = $"{Configuration["AppSettings:CouchDB:Url"]}/{Configuration["AppSettings:CouchDB:Database"]}/";
c.BaseAddress = new Uri(baseUrl);
});
services.AddTransient<IModelMapper, ModelMapper>();
services
.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.Formatting = Formatting.Indented;
options.SerializerSettings.ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true }
};
options.SerializerSettings.Converters = new[] { new StringEnumConverter() };
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
});
services.AddAuthentication("CookieOrJwt")
.AddPolicyScheme("CookieOrJwt", "Either cookie or jwt", options =>
{
options.ForwardDefaultSelector = context =>
{
var bearerAuth = context.Request.Headers["Authorization"].FirstOrDefault()?.StartsWith("Bearer ") ?? false;
return bearerAuth
? JwtBearerDefaults.AuthenticationScheme
: CookieAuthenticationDefaults.AuthenticationScheme;
};
})
.AddCookie(options =>
{
options.Cookie.Name = "session-id";
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
options.SlidingExpiration = true;
})
.AddMicrosoftIdentityWebApi(Configuration);
services.AddSwaggerDocument(config =>
{
// This just ensures anyone with a microsoft account can make API calls.
config.AddSecurity("bearer", new NSwag.OpenApiSecurityScheme
{
Type = NSwag.OpenApiSecuritySchemeType.OAuth2,
Flow = NSwag.OpenApiOAuth2Flow.Implicit,
AuthorizationUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
TokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token",
Scopes = new Dictionary<string, string> {
{ "api://c1e94676-cab0-42ba-8b6c-9532b8486fff/access_as_user", "The scope" },
{ "api://c1e94676-cab0-42ba-8b6c-9532b8486fff/ShogiAdmin", "Admin scope" }
},
Scheme = "bearer",
BearerFormat = "JWT",
In = NSwag.OpenApiSecurityApiKeyLocation.Header,
});
config.PostProcess = document =>
{
document.Info.Title = "Gameboard.ShogiUI.Sockets";
};
});
services.AddHttpLogging(options =>
{
options.LoggingFields = Microsoft.AspNetCore.HttpLogging.HttpLoggingFields.Request;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISocketService socketConnectionManager)
{
app.UseHttpLogging();
var origins = new[] {
"http://localhost:3000", "https://localhost:3000",
"http://127.0.0.1:3000", "https://127.0.0.1:3000",
"https://api.lucaserver.space", "https://lucaserver.space"
};
var socketOptions = new WebSocketOptions();
foreach (var o in origins)
socketOptions.AllowedOrigins.Add(o);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//var client = PublicClientApplicationBuilder
// .Create(Configuration["AzureAd:ClientId"])
// .WithLogging(
// (level, message, pii) =>
// {
// Console.WriteLine(message);
// },
// LogLevel.Verbose,
// true,
// true
// )
// .Build();
}
else
{
// Disable this because Apache handles HTTPS in production.
//app.UseHsts();
}
app
//.UseRequestResponseLogging()
.UseCors(opt => opt.WithOrigins(origins).AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("Set-Cookie").AllowCredentials())
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseOpenApi()
.UseSwaggerUi3(config =>
{
config.OAuth2Client = new NSwag.AspNetCore.OAuth2ClientSettings()
{
ClientId = "c1e94676-cab0-42ba-8b6c-9532b8486fff",
UsePkceWithAuthorizationCodeGrant = true
};
config.TransformToExternalPath = (route, request) =>
{
return request.Host.HasValue && request.Host.Value.Contains("localhost")
? route
: $"/Gameboard.ShogiUI.Sockets{route}";
};
//config.WithCredentials = true;
})
.UseWebSockets(socketOptions)
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
})
.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
await socketConnectionManager.HandleSocketRequest(context);
}
else
{
await next();
}
});
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = true
}
},
Converters = new[] { new StringEnumConverter() },
NullValueHandling = NullValueHandling.Ignore,
};
}
}
}

View File

@@ -14,10 +14,20 @@
},
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "Hauthlive.onmicrosoft.com",
"TenantId": "d6019544-c403-415c-8e96-50009635b6aa",
"ClientId": "c1e94676-cab0-42ba-8b6c-9532b8486fff",
"TenantId": "common",
"Scopes": "",
"CallbackPath": "/signin-oidc"
},
"AzureAd2": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "c1e94676-cab0-42ba-8b6c-9532b8486fff",
"TenantId": "d6019544-c403-415c-8e96-50009635b6aa",
"Audience": "c1e94676-cab0-42ba-8b6c-9532b8486fff",
"ClientSecret": ""
"ClientSecret": "",
"Domain": "Hauthlive.onmicrosoft.com",
"CallbackPath": "/signin-oidc"
},
"AllowedHosts": "*"
}