checkpoint

This commit is contained in:
2021-12-19 17:52:23 -06:00
parent 433ab2772a
commit a18b7974c8
5 changed files with 231 additions and 155 deletions

View File

@@ -0,0 +1,39 @@
namespace Gameboard.ShogiUI.Sockets
{
namespace anonymous_session.Middlewares
{
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
/// <summary>
/// TODO: Use this example in the guest session logic instead of custom claims.
/// </summary>
public class AnonymousSessionMiddleware
{
private readonly RequestDelegate _next;
public AnonymousSessionMiddleware(RequestDelegate next)
{
_next = next;
}
public async System.Threading.Tasks.Task InvokeAsync(HttpContext context)
{
if (!context.User.Identity.IsAuthenticated)
{
if (string.IsNullOrEmpty(context.User.FindFirstValue(ClaimTypes.Anonymous)))
{
var claim = new Claim(ClaimTypes.Anonymous, System.Guid.NewGuid().ToString());
context.User.AddIdentity(new ClaimsIdentity(new[] { claim }));
string scheme = Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme;
await context.SignInAsync(scheme, context.User, new AuthenticationProperties { IsPersistent = false });
}
}
await _next(context);
}
}
}
}

View File

@@ -241,6 +241,21 @@ namespace Gameboard.ShogiUI.Sockets.Controllers
return Ok(); return Ok();
} }
[Authorize(Roles = "Admin, Shogi")]
[HttpDelete("{gameName}")]
public async Task<IActionResult> DeleteSession([FromRoute] string gameName)
{
var user = await ReadUserOrThrow();
if (user.IsAdmin)
{
return Ok();
}
else
{
return Unauthorized();
}
}
private async Task<Models.User> ReadUserOrThrow() private async Task<Models.User> ReadUserOrThrow()
{ {
var user = await gameboardManager.ReadUser(User); var user = await gameboardManager.ReadUser(User);

View File

@@ -37,6 +37,8 @@ namespace Gameboard.ShogiUI.Sockets.Models
public bool IsGuest => LoginPlatform == WhichLoginPlatform.Guest; public bool IsGuest => LoginPlatform == WhichLoginPlatform.Guest;
public bool IsAdmin => LoginPlatform == WhichLoginPlatform.Microsoft && Id == "Hauth@live.com";
public User(string id, string displayName, WhichLoginPlatform platform) public User(string id, string displayName, WhichLoginPlatform platform)
{ {
Id = id; Id = id;

View File

@@ -30,169 +30,188 @@ using System.Threading.Tasks;
namespace Gameboard.ShogiUI.Sockets namespace Gameboard.ShogiUI.Sockets
{ {
public class Startup public class Startup
{ {
public Startup(IConfiguration configuration) public Startup(IConfiguration configuration)
{ {
Configuration = configuration; Configuration = configuration;
} }
public IConfiguration Configuration { get; } public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddSingleton<IJoinByCodeHandler, JoinByCodeHandler>(); services.AddSingleton<IJoinByCodeHandler, JoinByCodeHandler>();
services.AddSingleton<ISocketConnectionManager, SocketConnectionManager>(); services.AddSingleton<ISocketConnectionManager, SocketConnectionManager>();
services.AddSingleton<ISocketTokenCache, SocketTokenCache>(); services.AddSingleton<ISocketTokenCache, SocketTokenCache>();
services.AddSingleton<IGameboardManager, GameboardManager>(); services.AddSingleton<IGameboardManager, GameboardManager>();
services.AddSingleton<IValidator<JoinByCodeRequest>, JoinByCodeRequestValidator>(); services.AddSingleton<IValidator<JoinByCodeRequest>, JoinByCodeRequestValidator>();
services.AddSingleton<IValidator<JoinGameRequest>, JoinGameRequestValidator>(); services.AddSingleton<IValidator<JoinGameRequest>, JoinGameRequestValidator>();
services.AddSingleton<ISocketService, SocketService>(); services.AddSingleton<ISocketService, SocketService>();
services.AddTransient<IGameboardRepository, GameboardRepository>(); services.AddTransient<IGameboardRepository, GameboardRepository>();
services.AddSingleton<IClaimsTransformation, ShogiUserClaimsTransformer>(); services.AddSingleton<IClaimsTransformation, ShogiUserClaimsTransformer>();
services.AddHttpClient("couchdb", c => services.AddHttpClient("couchdb", c =>
{ {
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("admin:admin")); var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("admin:admin"));
c.DefaultRequestHeaders.Add("Accept", "application/json"); c.DefaultRequestHeaders.Add("Accept", "application/json");
c.DefaultRequestHeaders.Add("Authorization", $"Basic {base64}"); c.DefaultRequestHeaders.Add("Authorization", $"Basic {base64}");
var baseUrl = $"{Configuration["AppSettings:CouchDB:Url"]}/{Configuration["AppSettings:CouchDB:Database"]}/"; var baseUrl = $"{Configuration["AppSettings:CouchDB:Url"]}/{Configuration["AppSettings:CouchDB:Database"]}/";
c.BaseAddress = new Uri(baseUrl); c.BaseAddress = new Uri(baseUrl);
}); });
services services
.AddControllers() .AddControllers()
.AddNewtonsoftJson(options => .AddNewtonsoftJson(options =>
{ {
options.SerializerSettings.Formatting = Formatting.Indented; options.SerializerSettings.Formatting = Formatting.Indented;
options.SerializerSettings.ContractResolver = new DefaultContractResolver options.SerializerSettings.ContractResolver = new DefaultContractResolver
{ {
NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true } NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true }
}; };
options.SerializerSettings.Converters = new[] { new StringEnumConverter() }; options.SerializerSettings.Converters = new[] { new StringEnumConverter() };
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
}); });
services.AddAuthentication("CookieOrJwt") services.AddAuthentication("CookieOrJwt")
.AddPolicyScheme("CookieOrJwt", "Either cookie or jwt", options => .AddPolicyScheme("CookieOrJwt", "Either cookie or jwt", options =>
{ {
options.ForwardDefaultSelector = context => options.ForwardDefaultSelector = context =>
{ {
var bearerAuth = context.Request.Headers["Authorization"].FirstOrDefault()?.StartsWith("Bearer ") ?? false; var bearerAuth = context.Request.Headers["Authorization"].FirstOrDefault()?.StartsWith("Bearer ") ?? false;
return bearerAuth return bearerAuth
? JwtBearerDefaults.AuthenticationScheme ? JwtBearerDefaults.AuthenticationScheme
: CookieAuthenticationDefaults.AuthenticationScheme; : CookieAuthenticationDefaults.AuthenticationScheme;
}; };
}) })
.AddCookie(options => .AddCookie(options =>
{ {
options.Cookie.Name = "session-id"; options.Cookie.Name = "session-id";
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None; options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always; options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
options.SlidingExpiration = true; options.SlidingExpiration = true;
}) })
.AddMicrosoftIdentityWebApi(Configuration); .AddMicrosoftIdentityWebApi(Configuration);
services.AddSwaggerDocument(config => services.AddSwaggerDocument(config =>
{ {
config.AddSecurity("Bearer", new NSwag.OpenApiSecurityScheme //config.AddSecurity("bearer", Enumerable.Empty<string>(), new NSwag.OpenApiSecurityScheme
{ //{
Type = NSwag.OpenApiSecuritySchemeType.OAuth2, // Type = NSwag.OpenApiSecuritySchemeType.OAuth2,
Flow = NSwag.OpenApiOAuth2Flow.AccessCode, // Flow = NSwag.OpenApiOAuth2Flow.Implicit,
AuthorizationUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", // Flows = new NSwag.OpenApiOAuthFlows
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" } }, // Implicit = new NSwag.OpenApiOAuthFlow
Scheme = "Bearer" // {
}); // Scopes =
config.PostProcess = document => // }
{ // }
document.Info.Title = "Gameboard.ShogiUI.Sockets"; //});
};
});
// Remove default HttpClient logging. // This just ensures anyone with a microsoft account can make API calls.
services.RemoveAll<IHttpMessageHandlerBuilderFilter>(); 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";
};
});
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // Remove default HttpClient logging.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISocketService socketConnectionManager) services.RemoveAll<IHttpMessageHandlerBuilderFilter>();
{ }
var origins = new[] {
"http://localhost:3000", "https://localhost:3000",
"http://127.0.0.1:3000", "https://127.0.0.1:3000",
"https://dev.lucaserver.space", "https://lucaserver.space"
};
var socketOptions = new WebSocketOptions();
foreach (var o in origins)
socketOptions.AllowedOrigins.Add(o);
if (env.IsDevelopment()) // 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.UseDeveloperExceptionPage(); {
var client = PublicClientApplicationBuilder var origins = new[] {
.Create(Configuration["AzureAd:ClientId"]) "http://localhost:3000", "https://localhost:3000",
.WithLogging( "http://127.0.0.1:3000", "https://127.0.0.1:3000",
(level, message, pii) => "https://dev.lucaserver.space", "https://lucaserver.space"
{ };
var socketOptions = new WebSocketOptions();
foreach (var o in origins)
socketOptions.AllowedOrigins.Add(o);
}, if (env.IsDevelopment())
LogLevel.Verbose, {
true, app.UseDeveloperExceptionPage();
true var client = PublicClientApplicationBuilder
) .Create(Configuration["AzureAd:ClientId"])
.Build(); .WithLogging(
} (level, message, pii) =>
else {
{ Console.WriteLine(message);
app.UseHsts(); },
} LogLevel.Verbose,
app true,
.UseRequestResponseLogging() true
.UseCors(opt => opt.WithOrigins(origins).AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("Set-Cookie").AllowCredentials()) )
.UseRouting() .Build();
.UseAuthentication() }
.UseAuthorization() else
.UseOpenApi() {
.UseSwaggerUi3(config => app.UseHsts();
{ }
config.OAuth2Client = new NSwag.AspNetCore.OAuth2ClientSettings() app
{ .UseRequestResponseLogging()
ClientId = "c1e94676-cab0-42ba-8b6c-9532b8486fff", .UseCors(opt => opt.WithOrigins(origins).AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("Set-Cookie").AllowCredentials())
UsePkceWithAuthorizationCodeGrant = true .UseRouting()
}; .UseAuthentication()
//config.WithCredentials = true; .UseAuthorization()
}) .UseOpenApi()
.UseWebSockets(socketOptions) .UseSwaggerUi3(config =>
.UseEndpoints(endpoints => {
{ config.OAuth2Client = new NSwag.AspNetCore.OAuth2ClientSettings()
endpoints.MapControllers(); {
}) ClientId = "c1e94676-cab0-42ba-8b6c-9532b8486fff",
.Use(async (context, next) => UsePkceWithAuthorizationCodeGrant = true
{ };
if (context.WebSockets.IsWebSocketRequest) //config.WithCredentials = true;
{ })
await socketConnectionManager.HandleSocketRequest(context); .UseWebSockets(socketOptions)
} .UseEndpoints(endpoints =>
else {
{ endpoints.MapControllers();
await next(); })
} .Use(async (context, next) =>
}); {
if (context.WebSockets.IsWebSocketRequest)
{
await socketConnectionManager.HandleSocketRequest(context);
}
else
{
await next();
}
});
JsonConvert.DefaultSettings = () => new JsonSerializerSettings JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{ {
Formatting = Formatting.Indented, Formatting = Formatting.Indented,
ContractResolver = new DefaultContractResolver ContractResolver = new DefaultContractResolver
{ {
NamingStrategy = new CamelCaseNamingStrategy NamingStrategy = new CamelCaseNamingStrategy
{ {
ProcessDictionaryKeys = true ProcessDictionaryKeys = true
} }
}, },
Converters = new[] { new StringEnumConverter() }, Converters = new[] { new StringEnumConverter() },
NullValueHandling = NullValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore,
}; };
} }
} }
} }

View File

@@ -16,7 +16,8 @@
"Instance": "https://login.microsoftonline.com/", "Instance": "https://login.microsoftonline.com/",
"ClientId": "c1e94676-cab0-42ba-8b6c-9532b8486fff", "ClientId": "c1e94676-cab0-42ba-8b6c-9532b8486fff",
"TenantId": "common", "TenantId": "common",
"Audience": "c1e94676-cab0-42ba-8b6c-9532b8486fff" "Audience": "c1e94676-cab0-42ba-8b6c-9532b8486fff",
"ClientSecret": ""
}, },
"AllowedHosts": "*" "AllowedHosts": "*"
} }