diff --git a/backend/src/Tnsu.ApprovalRoute.Api/Auth/AuthSchemes.cs b/backend/src/Tnsu.ApprovalRoute.Api/Auth/AuthSchemes.cs new file mode 100644 index 0000000..331bf3d --- /dev/null +++ b/backend/src/Tnsu.ApprovalRoute.Api/Auth/AuthSchemes.cs @@ -0,0 +1,6 @@ +namespace Tnsu.ApprovalRoute.Api.Auth; + +public static class AuthSchemes +{ + public const string Dev = "Dev"; +} diff --git a/backend/src/Tnsu.ApprovalRoute.Api/Auth/AuthSetup.cs b/backend/src/Tnsu.ApprovalRoute.Api/Auth/AuthSetup.cs new file mode 100644 index 0000000..e72b17c --- /dev/null +++ b/backend/src/Tnsu.ApprovalRoute.Api/Auth/AuthSetup.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; + +namespace Tnsu.ApprovalRoute.Api.Auth; + +public static class AuthSetup +{ + public static IServiceCollection AddApprovalRouteAuth(this IServiceCollection services, IConfiguration configuration) + { + var tenantId = configuration["Entra:TenantId"]; + var audience = configuration["Entra:Audience"]; + + if (!string.IsNullOrWhiteSpace(tenantId) && !string.IsNullOrWhiteSpace(audience)) + { + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = $"https://login.microsoftonline.com/{tenantId}/v2.0"; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidIssuers = + [ + $"https://login.microsoftonline.com/{tenantId}/v2.0", + $"https://sts.windows.net/{tenantId}/" + ], + ValidAudiences = [audience, $"api://{audience}"] + }; + }); + } + else + { + services.AddAuthentication(AuthSchemes.Dev) + .AddScheme(AuthSchemes.Dev, _ => { }); + } + + services.AddAuthorization(); + + return services; + } +} diff --git a/backend/src/Tnsu.ApprovalRoute.Api/Auth/CurrentUserAccessor.cs b/backend/src/Tnsu.ApprovalRoute.Api/Auth/CurrentUserAccessor.cs new file mode 100644 index 0000000..42e31cd --- /dev/null +++ b/backend/src/Tnsu.ApprovalRoute.Api/Auth/CurrentUserAccessor.cs @@ -0,0 +1,11 @@ +using System.Security.Claims; +using Tnsu.ApprovalRoute.Application.Common.Interfaces; + +namespace Tnsu.ApprovalRoute.Api.Auth; + +public sealed class CurrentUserAccessor(IHttpContextAccessor httpContextAccessor) : ICurrentUser +{ + public string? Email => + httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.Email) + ?? httpContextAccessor.HttpContext?.User.FindFirstValue("preferred_username"); +} diff --git a/backend/src/Tnsu.ApprovalRoute.Api/Auth/DevAuthHandler.cs b/backend/src/Tnsu.ApprovalRoute.Api/Auth/DevAuthHandler.cs new file mode 100644 index 0000000..ed829eb --- /dev/null +++ b/backend/src/Tnsu.ApprovalRoute.Api/Auth/DevAuthHandler.cs @@ -0,0 +1,27 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Options; + +namespace Tnsu.ApprovalRoute.Api.Auth; + +// Используется только когда Entra:TenantId/Entra:Audience не заданы (локальная разработка без реального Entra ID). +public sealed class DevAuthHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + IConfiguration configuration) + : AuthenticationHandler(options, logger, encoder) +{ + protected override Task HandleAuthenticateAsync() + { + var email = Request.Headers["X-Dev-User-Email"].FirstOrDefault() + ?? configuration["Dev:DefaultUserEmail"] + ?? "dev@tnsu.kz"; + + var identity = new ClaimsIdentity([new Claim(ClaimTypes.Email, email)], AuthSchemes.Dev); + var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), AuthSchemes.Dev); + + return Task.FromResult(AuthenticateResult.Success(ticket)); + } +} diff --git a/backend/src/Tnsu.ApprovalRoute.Api/Controllers/RequestTypesController.cs b/backend/src/Tnsu.ApprovalRoute.Api/Controllers/RequestTypesController.cs index 5dac9c2..fda5461 100644 --- a/backend/src/Tnsu.ApprovalRoute.Api/Controllers/RequestTypesController.cs +++ b/backend/src/Tnsu.ApprovalRoute.Api/Controllers/RequestTypesController.cs @@ -1,4 +1,5 @@ using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Tnsu.ApprovalRoute.Api.Common; using Tnsu.ApprovalRoute.Application.RequestTypes; @@ -6,6 +7,7 @@ using Tnsu.ApprovalRoute.Application.RequestTypes; namespace Tnsu.ApprovalRoute.Api.Controllers; [ApiController] +[Authorize] [Route("api/request-types")] public sealed class RequestTypesController(ISender mediator) : ControllerBase { diff --git a/backend/src/Tnsu.ApprovalRoute.Api/Controllers/TemplateRouteStepsController.cs b/backend/src/Tnsu.ApprovalRoute.Api/Controllers/TemplateRouteStepsController.cs index 5c2b198..f069f64 100644 --- a/backend/src/Tnsu.ApprovalRoute.Api/Controllers/TemplateRouteStepsController.cs +++ b/backend/src/Tnsu.ApprovalRoute.Api/Controllers/TemplateRouteStepsController.cs @@ -1,4 +1,5 @@ using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Tnsu.ApprovalRoute.Api.Common; using Tnsu.ApprovalRoute.Application.TemplateRouteSteps; @@ -6,6 +7,7 @@ using Tnsu.ApprovalRoute.Application.TemplateRouteSteps; namespace Tnsu.ApprovalRoute.Api.Controllers; [ApiController] +[Authorize] [Route("api/template-routes/{routeId:guid}/steps")] public sealed class TemplateRouteStepsController(ISender mediator) : ControllerBase { diff --git a/backend/src/Tnsu.ApprovalRoute.Api/Controllers/TemplateRoutesController.cs b/backend/src/Tnsu.ApprovalRoute.Api/Controllers/TemplateRoutesController.cs index 9d26b2e..6cac78c 100644 --- a/backend/src/Tnsu.ApprovalRoute.Api/Controllers/TemplateRoutesController.cs +++ b/backend/src/Tnsu.ApprovalRoute.Api/Controllers/TemplateRoutesController.cs @@ -1,4 +1,5 @@ using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Tnsu.ApprovalRoute.Api.Common; using Tnsu.ApprovalRoute.Application.TemplateRoutes; @@ -6,6 +7,7 @@ using Tnsu.ApprovalRoute.Application.TemplateRoutes; namespace Tnsu.ApprovalRoute.Api.Controllers; [ApiController] +[Authorize] [Route("api/template-routes")] public sealed class TemplateRoutesController(ISender mediator) : ControllerBase { diff --git a/backend/src/Tnsu.ApprovalRoute.Api/Program.cs b/backend/src/Tnsu.ApprovalRoute.Api/Program.cs index e9793c2..01a6c2d 100644 --- a/backend/src/Tnsu.ApprovalRoute.Api/Program.cs +++ b/backend/src/Tnsu.ApprovalRoute.Api/Program.cs @@ -3,13 +3,18 @@ using Extensions.Hosting.AsyncInitialization; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Tnsu.ApprovalRoute.Api.Auth; using Tnsu.ApprovalRoute.Application; +using Tnsu.ApprovalRoute.Application.Common.Interfaces; using Tnsu.ApprovalRoute.Infrastructure; var builder = WebApplication.CreateBuilder(args); builder.Services.AddApprovalRouteInfrastructure(builder.Configuration); builder.Services.AddApprovalRouteApplication(); +builder.Services.AddApprovalRouteAuth(builder.Configuration); +builder.Services.AddHttpContextAccessor(); +builder.Services.AddScoped(); builder.Services.AddOpenApi(); builder.Services.AddHealthChecks(); builder.Services.AddControllers() @@ -21,6 +26,10 @@ if (app.Environment.IsDevelopment()) app.MapOpenApi(); app.MapHealthChecks("/health"); + +app.UseAuthentication(); +app.UseAuthorization(); + app.MapControllers(); await app.InitAndRunAsync(); diff --git a/backend/src/Tnsu.ApprovalRoute.Api/appsettings.json b/backend/src/Tnsu.ApprovalRoute.Api/appsettings.json index 7cd39c3..9631835 100644 --- a/backend/src/Tnsu.ApprovalRoute.Api/appsettings.json +++ b/backend/src/Tnsu.ApprovalRoute.Api/appsettings.json @@ -7,5 +7,12 @@ }, "ConnectionStrings": { "Postgres": "Host=localhost;Port=5435;Database=tnsu_approval_route;Username=tnsu_approval_route;Password=tnsu_approval_route" + }, + "Entra": { + "TenantId": "", + "Audience": "" + }, + "Dev": { + "DefaultUserEmail": "dev@tnsu.kz" } } diff --git a/backend/src/Tnsu.ApprovalRoute.Application/Common/Interfaces/ICurrentUser.cs b/backend/src/Tnsu.ApprovalRoute.Application/Common/Interfaces/ICurrentUser.cs new file mode 100644 index 0000000..673c68c --- /dev/null +++ b/backend/src/Tnsu.ApprovalRoute.Application/Common/Interfaces/ICurrentUser.cs @@ -0,0 +1,6 @@ +namespace Tnsu.ApprovalRoute.Application.Common.Interfaces; + +public interface ICurrentUser +{ + string? Email { get; } +}