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.HttpLogging; using Microsoft.Identity.Web; using Microsoft.OpenApi.Models; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using System.Text; namespace Gameboard.ShogiUI.Sockets { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); 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.OAuthScopes("api://c1e94676-cab0-42ba-8b6c-9532b8486fff/DefaultScope"); options.OAuthConfigObject.ClientId = builder.Configuration["AzureAd:SwaggerUIClientId"]; 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(); 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(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton, JoinByCodeRequestValidator>(); services.AddSingleton, JoinGameRequestValidator>(); services.AddSingleton(); services.AddTransient(); services.AddSingleton(); 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(); } 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"), Scopes = new Dictionary { { "api://c1e94676-cab0-42ba-8b6c-9532b8486fff/DefaultScope", "Default Scope" } } } }, 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() } }); }); } } }