From 0aac1b6dc7a642d980bbfda09bda9a8b050b4886 Mon Sep 17 00:00:00 2001 From: minhtrannhat Date: Mon, 23 Dec 2024 12:00:00 -0500 Subject: [PATCH] feat(api): add authentication middleware and request context --- .../Auth/ClaimsPrincipalExtensions.cs | 22 +++++++++++ src/IncidentOps.Api/Auth/RequestContext.cs | 10 +++++ src/IncidentOps.Api/Auth/RoleRequirement.cs | 38 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 src/IncidentOps.Api/Auth/ClaimsPrincipalExtensions.cs create mode 100644 src/IncidentOps.Api/Auth/RequestContext.cs create mode 100644 src/IncidentOps.Api/Auth/RoleRequirement.cs diff --git a/src/IncidentOps.Api/Auth/ClaimsPrincipalExtensions.cs b/src/IncidentOps.Api/Auth/ClaimsPrincipalExtensions.cs new file mode 100644 index 0000000..05e0dad --- /dev/null +++ b/src/IncidentOps.Api/Auth/ClaimsPrincipalExtensions.cs @@ -0,0 +1,22 @@ +using System.Security.Claims; +using IncidentOps.Domain.Enums; + +namespace IncidentOps.Api.Auth; + +public static class ClaimsPrincipalExtensions +{ + public static RequestContext GetRequestContext(this ClaimsPrincipal principal) + { + var userId = Guid.Parse(principal.FindFirstValue("sub") ?? throw new InvalidOperationException("Missing sub claim")); + var orgId = Guid.Parse(principal.FindFirstValue("org_id") ?? throw new InvalidOperationException("Missing org_id claim")); + var roleStr = principal.FindFirstValue("org_role") ?? throw new InvalidOperationException("Missing org_role claim"); + var role = Enum.Parse(roleStr, ignoreCase: true); + + return new RequestContext + { + UserId = userId, + OrgId = orgId, + Role = role + }; + } +} diff --git a/src/IncidentOps.Api/Auth/RequestContext.cs b/src/IncidentOps.Api/Auth/RequestContext.cs new file mode 100644 index 0000000..ea0c8cd --- /dev/null +++ b/src/IncidentOps.Api/Auth/RequestContext.cs @@ -0,0 +1,10 @@ +using IncidentOps.Domain.Enums; + +namespace IncidentOps.Api.Auth; + +public class RequestContext +{ + public Guid UserId { get; set; } + public Guid OrgId { get; set; } + public OrgRole Role { get; set; } +} diff --git a/src/IncidentOps.Api/Auth/RoleRequirement.cs b/src/IncidentOps.Api/Auth/RoleRequirement.cs new file mode 100644 index 0000000..ee51b74 --- /dev/null +++ b/src/IncidentOps.Api/Auth/RoleRequirement.cs @@ -0,0 +1,38 @@ +using IncidentOps.Domain.Enums; +using Microsoft.AspNetCore.Authorization; + +namespace IncidentOps.Api.Auth; + +public class RoleRequirement : IAuthorizationRequirement +{ + public OrgRole MinimumRole { get; } + + public RoleRequirement(OrgRole minimumRole) + { + MinimumRole = minimumRole; + } +} + +public class RoleRequirementHandler : AuthorizationHandler +{ + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement) + { + var roleClaim = context.User.FindFirst("org_role")?.Value; + if (roleClaim == null) + { + return Task.CompletedTask; + } + + if (!Enum.TryParse(roleClaim, ignoreCase: true, out var userRole)) + { + return Task.CompletedTask; + } + + if (userRole >= requirement.MinimumRole) + { + context.Succeed(requirement); + } + + return Task.CompletedTask; + } +}