diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Security/Endpoints/AuthEndpoints.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Security/Endpoints/AuthEndpoints.cs new file mode 100644 index 0000000..74a5470 --- /dev/null +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Security/Endpoints/AuthEndpoints.cs @@ -0,0 +1,83 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using ZB.MOM.WW.OtOpcUa.Security.Jwt; +using ZB.MOM.WW.OtOpcUa.Security.Ldap; + +namespace ZB.MOM.WW.OtOpcUa.Security.Endpoints; + +public static class AuthEndpoints +{ + public sealed record LoginRequest(string Username, string Password); + + public sealed record TokenResponse(string Token); + + public static IEndpointRouteBuilder MapOtOpcUaAuth(this IEndpointRouteBuilder app) + { + app.MapPost("/auth/login", (Delegate)LoginAsync).AllowAnonymous(); + app.MapGet("/auth/ping", (Delegate)Ping).AllowAnonymous(); + app.MapPost("/auth/token", (Delegate)IssueToken).RequireAuthorization(); + app.MapPost("/auth/logout", (Delegate)LogoutAsync).RequireAuthorization(); + return app; + } + + private static async Task LoginAsync( + LoginRequest request, + HttpContext http, + ILdapAuthService ldap, + CancellationToken ct) + { + LdapAuthResult result; + try + { + result = await ldap.AuthenticateAsync(request.Username, request.Password, ct); + } + catch (Exception) + { + return Results.StatusCode(StatusCodes.Status503ServiceUnavailable); + } + + if (!result.Success) + return Results.Unauthorized(); + + var claims = new List + { + new(ClaimTypes.NameIdentifier, result.Username ?? request.Username), + new(JwtTokenService.UsernameClaimType, result.Username ?? request.Username), + new(JwtTokenService.DisplayNameClaimType, result.DisplayName ?? request.Username), + }; + foreach (var role in result.Roles) + claims.Add(new Claim(ClaimTypes.Role, role)); + + var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var principal = new ClaimsPrincipal(identity); + + await http.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal); + return Results.NoContent(); + } + + private static IResult Ping(HttpContext http) => + http.User.Identity?.IsAuthenticated == true ? Results.Ok() : Results.Unauthorized(); + + private static IResult IssueToken(HttpContext http, JwtTokenService jwt) + { + var user = http.User; + var username = user.FindFirst(JwtTokenService.UsernameClaimType)?.Value + ?? user.Identity?.Name + ?? string.Empty; + var displayName = user.FindFirst(JwtTokenService.DisplayNameClaimType)?.Value ?? username; + var roles = user.FindAll(ClaimTypes.Role).Select(c => c.Value).ToArray(); + + return Results.Ok(new TokenResponse(jwt.Issue(displayName, username, roles))); + } + + private static async Task LogoutAsync(HttpContext http) + { + await http.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + return Results.NoContent(); + } +}