diff --git a/RIIT/RIIT/Components/Layout/NavMenu.razor b/RIIT/RIIT/Components/Layout/NavMenu.razor index 4e924a1..1bf9976 100644 --- a/RIIT/RIIT/Components/Layout/NavMenu.razor +++ b/RIIT/RIIT/Components/Layout/NavMenu.razor @@ -31,7 +31,7 @@ diff --git a/RIIT/RIIT/Components/Pages/Auth.razor b/RIIT/RIIT/Components/Pages/Auth.razor index b7bbe6e..19caed0 100644 --- a/RIIT/RIIT/Components/Pages/Auth.razor +++ b/RIIT/RIIT/Components/Pages/Auth.razor @@ -1,4 +1,4 @@ -@page "/auth" +@page "/secure" @using Microsoft.AspNetCore.Authorization diff --git a/RIIT/RIIT/Program.cs b/RIIT/RIIT/Program.cs index 7ddcec4..4529960 100644 --- a/RIIT/RIIT/Program.cs +++ b/RIIT/RIIT/Program.cs @@ -1,3 +1,6 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Negotiate; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; @@ -7,6 +10,13 @@ using RIIT.Data; var builder = WebApplication.CreateBuilder(args); +// ===== Auth mode switches (configurable via appsettings.json) ===== +var authMode = builder.Configuration["Auth:Mode"]?.Trim() ?? "Internal"; +var windowsEnabled = string.Equals(authMode, "Windows", StringComparison.OrdinalIgnoreCase); + +// Detect IIS / IIS Express hosting (intranet scenario) +var runningUnderIis = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPNETCORE_IIS_PHYSICAL_PATH")); + // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); @@ -15,23 +25,42 @@ builder.Services.AddCascadingAuthenticationState(); builder.Services.AddScoped(); builder.Services.AddScoped(); +// Authentication: primary = Identity cookie builder.Services.AddAuthentication(options => - { - options.DefaultScheme = IdentityConstants.ApplicationScheme; - options.DefaultSignInScheme = IdentityConstants.ExternalScheme; - }) +{ + options.DefaultScheme = IdentityConstants.ApplicationScheme; + options.DefaultSignInScheme = IdentityConstants.ExternalScheme; + + // Keep challenge on cookie/login page; Windows is performed explicitly via /auth/windows. + options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme; +}) .AddIdentityCookies(); -var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); +// Windows Integrated (Kerberos/NTLM): +// - On IIS / IIS Express: do NOT register AddNegotiate(); IIS handles Windows auth and populates HttpContext.User. +// - Outside IIS: you may register AddNegotiate() (optional), but typical intranet hosting uses IIS anyway. +if (windowsEnabled && !runningUnderIis) +{ + builder.Services.AddAuthentication() + .AddNegotiate(); +} + +builder.Services.AddAuthorization(); + +var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") + ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddIdentityCore(options => - { - options.SignIn.RequireConfirmedAccount = true; - options.Stores.SchemaVersion = IdentitySchemaVersions.Version3; - }) +{ + // Email confirmation disabled for now (can be enabled later) + options.SignIn.RequireConfirmedAccount = false; + options.Stores.SchemaVersion = IdentitySchemaVersions.Version3; +}) .AddEntityFrameworkStores() .AddSignInManager() .AddDefaultTokenProviders(); @@ -40,6 +69,13 @@ builder.Services.AddSingleton, IdentityNoOpEmailSe var app = builder.Build(); +// Optionally guard against misconfiguration +// (Windows mode is intended for IIS/IIS Express intranet hosting) +if (windowsEnabled && !runningUnderIis) +{ + app.Logger.LogWarning("Auth:Mode=Windows but the app is not running under IIS/IIS Express. Windows Integrated auth may not work as expected."); +} + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -48,14 +84,85 @@ if (app.Environment.IsDevelopment()) else { app.UseExceptionHandler("/Error", createScopeForErrors: true); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } -app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); + +app.UseStatusCodePagesWithReExecute("/not-found"); app.UseHttpsRedirection(); +app.UseStaticFiles(); +app.UseRouting(); + + + +app.UseAuthentication(); + +if (windowsEnabled) +{ + app.Use(async (context, next) => + { + // Pokud už je přihlášený (cookie), nech vše projít. + if (context.User?.Identity?.IsAuthenticated == true) + { + await next(); + return; + } + + var path = context.Request.Path; + + // Nikdy neredirectovat: + // - bootstrap endpoint + // - identity stránky/endpoints + // - chybové stránky + // - blazor framework a static web assets + if (path.StartsWithSegments("/auth/windows") || + path.StartsWithSegments("/Account") || + path.StartsWithSegments("/not-found") || + path.StartsWithSegments("/Error") || + path.StartsWithSegments("/_framework") || + path.StartsWithSegments("/_content")) + { + await next(); + return; + } + + // Nezasahovat do statických souborů podle přípony (CSS/JS/fonts/images/maps) + var ext = System.IO.Path.GetExtension(path); + if (!string.IsNullOrEmpty(ext)) + { + await next(); + return; + } + + // Redirect jen pro "navigační" requesty (typicky HTML stránky) + if (!HttpMethods.IsGet(context.Request.Method)) + { + await next(); + return; + } + + // Pokud klient nechce HTML (např. fetch pro JSON), neredirectuj + var accept = context.Request.Headers.Accept.ToString(); + if (!string.IsNullOrEmpty(accept) && !accept.Contains("text/html", StringComparison.OrdinalIgnoreCase)) + { + await next(); + return; + } + + var returnUrl = context.Request.PathBase + context.Request.Path + context.Request.QueryString; + var target = "/auth/windows?returnUrl=" + Uri.EscapeDataString(returnUrl); + + + context.Response.Redirect(target); + }); +} + +app.UseAuthorization(); + app.UseAntiforgery(); + + app.MapStaticAssets(); app.MapRazorComponents() .AddInteractiveServerRenderMode(); @@ -63,4 +170,4 @@ app.MapRazorComponents() // Add additional endpoints required by the Identity /Account Razor components. app.MapAdditionalIdentityEndpoints(); -app.Run(); +app.Run(); \ No newline at end of file diff --git a/RIIT/RIIT/Properties/launchSettings.json b/RIIT/RIIT/Properties/launchSettings.json index 741617a..f918a60 100644 --- a/RIIT/RIIT/Properties/launchSettings.json +++ b/RIIT/RIIT/Properties/launchSettings.json @@ -1,23 +1,40 @@ { - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5250", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:7229;http://localhost:5250", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } + "windowsAuthentication": true, + "anonymousAuthentication": false + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7229;http://localhost:5250" + }, + "http": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5250" + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53505/", + "sslPort": 44361 } } +} \ No newline at end of file diff --git a/RIIT/RIIT/RIIT.csproj b/RIIT/RIIT/RIIT.csproj index 830eae4..4f69fcd 100644 --- a/RIIT/RIIT/RIIT.csproj +++ b/RIIT/RIIT/RIIT.csproj @@ -9,6 +9,7 @@ + diff --git a/RIIT/RIIT/appsettings.json b/RIIT/RIIT/appsettings.json index 227746a..f709e5d 100644 --- a/RIIT/RIIT/appsettings.json +++ b/RIIT/RIIT/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-RIIT-d783c396-a1d0-4ecc-bb0a-5fec040e8d36;Trusted_Connection=True;MultipleActiveResultSets=true" + "DefaultConnection": "Server=localhost\\SQLEXPRESS;Database=RIIT;Integrated Security=SSPI;TrustServerCertificate=True;" }, "Logging": { "LogLevel": { @@ -8,5 +8,11 @@ "Microsoft.AspNetCore": "Warning" } }, + "Auth": { + "Mode": "Windows", + "Windows": { + "RequireAuthenticatedUser": true + } + }, "AllowedHosts": "*" }