Some checks failed
Build / build (pull_request) Has been cancelled
Doku/Repo: - README.md mit Setup, Run-Anleitung, Konfiguration und Sprachpolicy neu geschrieben - appsettings.json: committed JWT-Key und irreführende LocalDB-Connection-String entfernt - appsettings.Development.json: expliziter Dev-Only JWT-Key hinterlegt - build.yml: Build-CI für master/PRs hinzugefügt - slnx/aspire.config.json: Pfad-Casing korrigiert (Linux-kompatibel) - .gitignore: Kommentar zu den Legacy-SQLite-Einträgen Server: - Jwt:Key ist in Production Pflicht (Fail-fast statt Fallback-Secret) - Passwort-Mindestlänge 8 Zeichen bei Registrierung - StartGame: nur aus Lobby, mindestens 2 Spieler - SetPlayerReady: gameId/Status geprüft - SubmitSlip: Game-/Round-Status, Round-Membership, IsUsed validiert - CreateChallenge: Round-Bindung, kein Self-Challenge, nur gespielte Karten, keine Doppel-Challenges - ResolveChallenge: Pending-Guard, Scoring (Score/SuccessfulSlips/FailedSlips), Approved-Transfer gefixt - GetPlayerHand: nur Karten der aktiven Runde - RoundTimeRemaining: StartedAt wird bei Round-Aktivierung gesetzt - Lobby-Code: crypto-random mit Kollisions-Retry - EF: explizite NpgsqlDataSource-Auflösung aus DI - Hub: Reconnect-Gruppenbeitritt, ConnectionId-Cleanup beim Disconnect, ChallengingPlayerName gesetzt, SlipChallenged/ChallengeResolved konsistent Client: - Offline-Start löscht Session nicht mehr (NetworkError vs. Invalid unterschieden) - ResolveChallenge geht durch die Offline-Queue (kein Crash mehr bei Disconnect) - Queue: Poison-Entries werden nach 3 Retries verworfen statt die Queue ewig zu blockieren - SignalR: Verbindung wird sauber disposed/rebuildet, Token immer zur Laufzeit gelesen, Connect-Lock gegen parallele Starts - ViewModels: Thread-Marshaling in allen Receive-Handlern, kein async void mehr - LobbyViewModel: Logout deaktiviert den Singleton (kein stale-Navigation mehr), Reaktivierung via Activate() - GameBoard: lokaler 1s-Timer für RoundTimeRemaining - Overlay: x:DataType für kompilierte Bindings, Frame→Border - csproj: Preview-Logging-Package auf 10.0.10, Template-Cruft entfernt
144 lines
4.4 KiB
C#
144 lines
4.4 KiB
C#
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using SlipItIn.Server.Data;
|
|
using SlipItIn.Server.Hubs;
|
|
using SlipItIn.Server.Services;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Aspire Service Defaults (OpenTelemetry, Health Checks, Service Discovery)
|
|
builder.AddServiceDefaults();
|
|
|
|
builder.AddNpgsqlDataSource(connectionName: "postgresdb");
|
|
|
|
builder.Services.AddDbContextFactory<SlipItInDbContext>((sp, options) =>
|
|
options.UseNpgsql(sp.GetRequiredService<Npgsql.NpgsqlDataSource>()));
|
|
|
|
// Services registrieren
|
|
builder.Services.AddTransient<IGameService, GameService>();
|
|
|
|
// JWT Authentication
|
|
var jwtKey = builder.Configuration["Jwt:Key"];
|
|
if (string.IsNullOrWhiteSpace(jwtKey) || jwtKey.Length < 32)
|
|
{
|
|
if (builder.Environment.IsDevelopment())
|
|
{
|
|
jwtKey = "DevOnlyKey-NotForProduction-ChangeMe-0123456789abcdef";
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Jwt:Key ist nicht konfiguriert oder kürzer als 32 Zeichen. " +
|
|
"In Production muss ein eigener Schlüssel via Konfiguration/Umgebungsvariable gesetzt werden.");
|
|
}
|
|
}
|
|
var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "SlipItInServer";
|
|
var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "SlipItInClient";
|
|
|
|
builder.Services
|
|
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
.AddJwtBearer(options =>
|
|
{
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuer = true,
|
|
ValidateAudience = true,
|
|
ValidateLifetime = true,
|
|
ValidateIssuerSigningKey = true,
|
|
ValidIssuer = jwtIssuer,
|
|
ValidAudience = jwtAudience,
|
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
|
|
};
|
|
|
|
// SignalR-Support für JWT in Query String
|
|
options.Events = new JwtBearerEvents
|
|
{
|
|
OnMessageReceived = context =>
|
|
{
|
|
var accessToken = context.Request.Query["access_token"];
|
|
var path = context.HttpContext.Request.Path;
|
|
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
|
|
{
|
|
context.Token = accessToken;
|
|
}
|
|
return Task.CompletedTask;
|
|
},
|
|
OnTokenValidated = async context =>
|
|
{
|
|
var userIdClaim = context.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
|
if (!int.TryParse(userIdClaim, out var userId))
|
|
{
|
|
context.Fail("Invalid user claim.");
|
|
return;
|
|
}
|
|
|
|
var dbFactory = context.HttpContext.RequestServices.GetRequiredService<IDbContextFactory<SlipItInDbContext>>();
|
|
await using var db = await dbFactory.CreateDbContextAsync();
|
|
|
|
var userExists = await db.Users.AnyAsync(u => u.Id == userId && u.IsActive);
|
|
if (!userExists)
|
|
{
|
|
context.Fail("User no longer exists or is inactive.");
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
builder.Services.AddAuthorization();
|
|
|
|
// SignalR
|
|
builder.Services.AddSignalR();
|
|
|
|
// CORS für Development
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddPolicy("AllowAll", policy =>
|
|
{
|
|
policy.AllowAnyOrigin()
|
|
.AllowAnyMethod()
|
|
.AllowAnyHeader();
|
|
});
|
|
});
|
|
|
|
// Controller & OpenAPI
|
|
builder.Services.AddControllers();
|
|
builder.Services.AddOpenApi();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Aspire Default Endpoints (Health Checks)
|
|
app.MapDefaultEndpoints();
|
|
|
|
// Migrations anwenden und Testdaten seeden (nur wenn die Datenbank leer ist)
|
|
using (var scope = app.Services.CreateScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<SlipItInDbContext>();
|
|
db.Database.Migrate();
|
|
await DbSeeder.SeedAsync(db);
|
|
}
|
|
|
|
// Configure the HTTP request pipeline.
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.MapOpenApi();
|
|
}
|
|
|
|
app.UseCors("AllowAll");
|
|
|
|
// Beim Emulator/geräteübergreifenden Zugriff würde die HTTPS-Umleitung
|
|
// den Client auf eine nicht vertrauenswürdige Dev-Zertifikats-URL schicken.
|
|
if (!app.Environment.IsDevelopment())
|
|
{
|
|
app.UseHttpsRedirection();
|
|
}
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
app.MapControllers();
|
|
app.MapHub<GameHub>("/hubs/game");
|
|
|
|
app.Run();
|