Merge branch 'main' of https://git.azuze.fr/kawa/SharepointToolbox-Web
This commit is contained in:
+61
-16
@@ -2,6 +2,8 @@ using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using Serilog;
|
||||
@@ -120,6 +122,7 @@ builder.Services.AddSingleton(new UserRepository(Path.Combine(dataFolder, "users
|
||||
builder.Services.AddSingleton(new AuditRepository(Path.Combine(dataFolder, "audit.jsonl")));
|
||||
|
||||
// ── Auth infrastructure ───────────────────────────────────────────────────────
|
||||
builder.Services.AddSingleton<IPasswordHasher<AppUser>, PasswordHasher<AppUser>>();
|
||||
builder.Services.AddSingleton<IUserService, UserService>();
|
||||
builder.Services.AddSingleton<IOAuthFlowCache, OAuthFlowCache>();
|
||||
builder.Services.AddSingleton<IEntraDeviceCodeFlow, EntraDeviceCodeFlow>();
|
||||
@@ -187,20 +190,32 @@ app.UseAuthorization();
|
||||
app.UseAntiforgery();
|
||||
|
||||
// ── Login / Logout endpoints ──────────────────────────────────────────────────
|
||||
if (app.Environment.IsDevelopment())
|
||||
var isDev = app.Environment.IsDevelopment();
|
||||
|
||||
// Combined login page. Dev: local form + "Quick sign in as Dev Admin" (no OIDC scheme registered).
|
||||
// Prod: local form + "Sign in with Microsoft".
|
||||
app.MapGet("/account/login", (HttpContext ctx, IAntiforgery antiforgery, string? returnUrl, bool? error) =>
|
||||
{
|
||||
app.MapGet("/account/login", async (HttpContext ctx, string? returnUrl, IUserService userService) =>
|
||||
var html = LoginPageRenderer.Build(
|
||||
ctx, antiforgery, returnUrl, error == true,
|
||||
showEntra: !isDev,
|
||||
showDevButton: isDev);
|
||||
return Results.Content(html, "text/html");
|
||||
});
|
||||
|
||||
if (isDev)
|
||||
{
|
||||
// Dev shortcut: provision + sign in the hardcoded Dev Admin (first run = Admin).
|
||||
app.MapGet("/account/login/dev", async (HttpContext ctx, string? returnUrl, IUserService userService) =>
|
||||
{
|
||||
const string devEmail = "dev@local.test";
|
||||
const string devName = "Dev Admin";
|
||||
|
||||
// Provision the dev user in users.json (first run = Admin)
|
||||
var provisionPrincipal = new ClaimsPrincipal(new ClaimsIdentity(
|
||||
new[] { new Claim("preferred_username", devEmail), new Claim("name", devName) },
|
||||
CookieAuthenticationDefaults.AuthenticationScheme));
|
||||
var user = await userService.ProvisionAsync(provisionPrincipal);
|
||||
|
||||
// Sign in with full claims including app_role for HTTP endpoints
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(
|
||||
new Claim[] {
|
||||
new("preferred_username", devEmail),
|
||||
@@ -212,16 +227,11 @@ if (app.Environment.IsDevelopment())
|
||||
await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
|
||||
ctx.Response.Redirect(string.IsNullOrEmpty(returnUrl) ? "/" : returnUrl);
|
||||
});
|
||||
|
||||
app.MapGet("/account/logout", async (HttpContext ctx) =>
|
||||
{
|
||||
await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
ctx.Response.Redirect("/");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
app.MapGet("/account/login", async (HttpContext ctx, string? returnUrl) =>
|
||||
// Microsoft / Entra OIDC challenge (the "Sign in with Microsoft" button).
|
||||
app.MapGet("/account/login/entra", async (HttpContext ctx, string? returnUrl) =>
|
||||
{
|
||||
var props = new AuthenticationProperties
|
||||
{
|
||||
@@ -229,14 +239,49 @@ else
|
||||
};
|
||||
await ctx.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, props);
|
||||
});
|
||||
}
|
||||
|
||||
app.MapGet("/account/logout", async (HttpContext ctx) =>
|
||||
{
|
||||
await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
// Local password sign-in — available in every environment.
|
||||
app.MapPost("/account/local-login", async (HttpContext ctx, IAntiforgery antiforgery, IUserService userService) =>
|
||||
{
|
||||
try { await antiforgery.ValidateRequestAsync(ctx); }
|
||||
catch (AntiforgeryValidationException) { return Results.BadRequest(); }
|
||||
|
||||
var form = await ctx.Request.ReadFormAsync();
|
||||
var email = form["email"].ToString();
|
||||
var password = form["password"].ToString();
|
||||
var returnUrl = form["returnUrl"].ToString();
|
||||
var safeReturn = string.IsNullOrEmpty(returnUrl) || !returnUrl.StartsWith('/') ? "/" : returnUrl;
|
||||
|
||||
var user = await userService.ValidateLocalCredentialsAsync(email, password);
|
||||
if (user is null)
|
||||
return Results.Redirect($"/account/login?error=true&returnUrl={Uri.EscapeDataString(safeReturn)}");
|
||||
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(
|
||||
new Claim[]
|
||||
{
|
||||
new("preferred_username", user.Email),
|
||||
new("name", user.DisplayName),
|
||||
new("app_role", user.Role.ToString()),
|
||||
new("auth_provider", nameof(AuthProvider.Local)),
|
||||
},
|
||||
CookieAuthenticationDefaults.AuthenticationScheme));
|
||||
|
||||
await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
|
||||
return Results.Redirect(safeReturn);
|
||||
});
|
||||
|
||||
app.MapGet("/account/logout", async (HttpContext ctx) =>
|
||||
{
|
||||
// Local/dev accounts only hold the cookie; Entra accounts also have an OIDC session to end.
|
||||
var isLocal = ctx.User.HasClaim("auth_provider", nameof(AuthProvider.Local));
|
||||
await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
if (!isDev && !isLocal && ctx.User.Identity?.IsAuthenticated == true)
|
||||
await ctx.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme,
|
||||
new AuthenticationProperties { RedirectUri = "/" });
|
||||
});
|
||||
}
|
||||
else
|
||||
ctx.Response.Redirect("/");
|
||||
});
|
||||
|
||||
// ── OAuth2 connect endpoints ──────────────────────────────────────────────────
|
||||
app.MapOAuthEndpoints();
|
||||
|
||||
Reference in New Issue
Block a user