Add alpha session gate with cookie auth and login UI.

This commit is contained in:
Leonid Pershin
2026-08-20 07:17:18 +03:00
parent 2a55a025f4
commit a444fc011e
24 changed files with 1005 additions and 27 deletions
+38 -3
View File
@@ -3,6 +3,7 @@ using HSchool.Server;
using HSchool.Server.Api;
using HSchool.Server.Game;
using HSchool.Server.Net;
using HSchool.Server.Session;
using HSchool.Simulation;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
@@ -12,6 +13,14 @@ var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.Services.AddProblemDetails();
builder.Services.AddOpenApi();
builder.Services.AddDataProtection();
builder.Services
.AddOptions<HSchoolOptions>()
.Bind(builder.Configuration.GetSection(HSchoolOptions.SectionName))
.Validate(options => !string.IsNullOrWhiteSpace(options.AlphaPassword), "HSchool:AlphaPassword must be set.")
.Validate(options => options.SessionCookieDays is > 0 and <= 365, "HSchool:SessionCookieDays must be between 1 and 365.")
.ValidateOnStart();
builder.Services
.AddOptions<SimulationOptions>()
@@ -30,6 +39,8 @@ builder.Services
builder.Services.AddSingleton<GameCommandQueue>();
builder.Services.AddSingleton<ClientRegistry>();
builder.Services.AddSingleton<UserStore>();
builder.Services.AddSingleton<SessionService>();
builder.Services.AddSingleton<GameMetrics>();
builder.Services.AddSingleton<SchoolStore>();
builder.Services.AddSingleton<ModContent>();
@@ -64,6 +75,9 @@ app.UseWebSockets(new WebSocketOptions
KeepAliveInterval = TimeSpan.FromSeconds(30),
});
app.UseMiddleware<SessionAuthMiddleware>();
app.MapSessionEndpoints();
app.MapSchoolEndpoints();
app.MapSettingsEndpoints();
app.MapTimetableEndpoints();
@@ -104,7 +118,7 @@ if (app.Configuration.GetValue("HSchool:AllowSaveReload", false))
}
// The realtime channel: one binary frame per protocol message, see docs/protocol.md.
app.Map("/ws/game", async (HttpContext context, GameSocketHandler handler) =>
app.Map("/ws/game", async (HttpContext context, GameSocketHandler handler, SessionService sessions) =>
{
if (!context.WebSockets.IsWebSocketRequest)
{
@@ -113,8 +127,29 @@ app.Map("/ws/game", async (HttpContext context, GameSocketHandler handler) =>
return;
}
using WebSocket socket = await context.WebSockets.AcceptWebSocketAsync();
await handler.HandleAsync(socket, context.RequestAborted);
if (!sessions.TryGetUserName(context, out var userName))
{
using WebSocket socket = await context.WebSockets.AcceptWebSocketAsync();
if (socket.State is WebSocketState.Open or WebSocketState.CloseReceived)
{
try
{
await socket.CloseAsync(
WebSocketCloseStatus.PolicyViolation,
"Session required.",
context.RequestAborted);
}
catch (WebSocketException)
{
// The peer may already be gone.
}
}
return;
}
using WebSocket connected = await context.WebSockets.AcceptWebSocketAsync();
await handler.HandleAsync(connected, userName, context.RequestAborted);
});
app.MapDefaultEndpoints();