diff --git a/src/Orbit.Api/Authorization/AdminAuthorizationHandler.cs b/src/Orbit.Api/Authorization/AdminAuthorizationHandler.cs new file mode 100644 index 00000000..8d4ef2eb --- /dev/null +++ b/src/Orbit.Api/Authorization/AdminAuthorizationHandler.cs @@ -0,0 +1,30 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Orbit.Domain.Entities; +using Orbit.Domain.Interfaces; + +namespace Orbit.Api.Authorization; + +/// +/// Marker requirement for the admin authorization boundary; satisfied by . +/// +public sealed class AdminRequirement : IAuthorizationRequirement; + +/// +/// Authorizes admin-only endpoints by reading User.IsAdmin live from the database on every request, +/// so revoking admin in the DB takes effect immediately rather than lingering until the caller's token expires. +/// The JWT carries identity only; it never asserts admin. +/// +public sealed class AdminAuthorizationHandler(IGenericRepository userRepository) + : AuthorizationHandler +{ + protected override async Task HandleRequirementAsync( + AuthorizationHandlerContext context, AdminRequirement requirement) + { + if (!Guid.TryParse(context.User.FindFirstValue(ClaimTypes.NameIdentifier), out var userId)) + return; + + if (await userRepository.AnyAsync(user => user.Id == userId && user.IsAdmin)) + context.Succeed(requirement); + } +} diff --git a/src/Orbit.Api/Extensions/ServiceCollectionExtensions.cs b/src/Orbit.Api/Extensions/ServiceCollectionExtensions.cs index 9f2e9d9c..131e6471 100644 --- a/src/Orbit.Api/Extensions/ServiceCollectionExtensions.cs +++ b/src/Orbit.Api/Extensions/ServiceCollectionExtensions.cs @@ -2,11 +2,13 @@ using FluentValidation; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Sentry; using Sentry.AspNetCore; using Orbit.Api.Authentication; +using Orbit.Api.Authorization; using Orbit.Api.OAuth; using Orbit.Application.Behaviors; using Orbit.Application.Common; @@ -157,8 +159,9 @@ public static WebApplicationBuilder AddOrbitAuthentication(this WebApplicationBu builder.Services.AddAuthorization(options => { options.AddPolicy(AdminPolicy.Name, policy => - policy.RequireClaim(AdminPolicy.ClaimType, AdminPolicy.ClaimValue)); + policy.Requirements.Add(new AdminRequirement())); }); + builder.Services.AddScoped(); return builder; } diff --git a/src/Orbit.Application/Auth/Commands/GoogleAuthCommand.cs b/src/Orbit.Application/Auth/Commands/GoogleAuthCommand.cs index 3e6cc244..50ff9786 100644 --- a/src/Orbit.Application/Auth/Commands/GoogleAuthCommand.cs +++ b/src/Orbit.Application/Auth/Commands/GoogleAuthCommand.cs @@ -44,7 +44,7 @@ public async Task> Handle(GoogleAuthCommand request, Cance if (wasReactivated || request.GoogleAccessToken is not null) await unitOfWork.SaveChangesAsync(cancellationToken); - var sessionResult = await authSessionService.CreateSessionAsync(user.Id, user.Email, user.IsAdmin, cancellationToken); + var sessionResult = await authSessionService.CreateSessionAsync(user.Id, user.Email, cancellationToken); if (sessionResult.IsFailure) return sessionResult.PropagateError(); diff --git a/src/Orbit.Application/Auth/Commands/VerifyCodeCommand.cs b/src/Orbit.Application/Auth/Commands/VerifyCodeCommand.cs index e7b40cc9..58eefbd5 100644 --- a/src/Orbit.Application/Auth/Commands/VerifyCodeCommand.cs +++ b/src/Orbit.Application/Auth/Commands/VerifyCodeCommand.cs @@ -40,7 +40,7 @@ public async Task> Handle(VerifyCodeCommand request, Cance var wasReactivated = await HandlePostLoginAsync(user, isNewUser, request, cancellationToken); - var sessionResult = await authSessionService.CreateSessionAsync(user.Id, user.Email, user.IsAdmin, cancellationToken); + var sessionResult = await authSessionService.CreateSessionAsync(user.Id, user.Email, cancellationToken); if (sessionResult.IsFailure) return sessionResult.PropagateError(); diff --git a/src/Orbit.Application/Common/AdminPolicy.cs b/src/Orbit.Application/Common/AdminPolicy.cs index 6b3dbd8a..94ed6917 100644 --- a/src/Orbit.Application/Common/AdminPolicy.cs +++ b/src/Orbit.Application/Common/AdminPolicy.cs @@ -1,14 +1,12 @@ namespace Orbit.Application.Common; /// -/// Names the single administrative authorization boundary. A user's IsAdmin flag mints the -/// admin claim into the JWT; the "Admin" policy (RequireClaim on that claim) gates admin-only -/// endpoints. The first admin is granted by a direct DB update (Users.IsAdmin = true); there is -/// no email-based gate. +/// Names the single administrative authorization boundary. The "Admin" policy reads +/// User.IsAdmin live from the database on every request, so revoking admin takes effect +/// immediately; the JWT carries identity only and never asserts admin. The first admin is granted +/// by a direct DB update (Users.IsAdmin = true). /// public static class AdminPolicy { public const string Name = "Admin"; - public const string ClaimType = "admin"; - public const string ClaimValue = "true"; } diff --git a/src/Orbit.Domain/Interfaces/IAuthSessionService.cs b/src/Orbit.Domain/Interfaces/IAuthSessionService.cs index 4fa123d2..8f923ab4 100644 --- a/src/Orbit.Domain/Interfaces/IAuthSessionService.cs +++ b/src/Orbit.Domain/Interfaces/IAuthSessionService.cs @@ -5,7 +5,7 @@ namespace Orbit.Domain.Interfaces; public interface IAuthSessionService { - Task> CreateSessionAsync(Guid userId, string email, bool isAdmin, CancellationToken cancellationToken = default); + Task> CreateSessionAsync(Guid userId, string email, CancellationToken cancellationToken = default); Task> RefreshSessionAsync(string refreshToken, CancellationToken cancellationToken = default); Task RevokeSessionAsync(string refreshToken, CancellationToken cancellationToken = default); } diff --git a/src/Orbit.Domain/Interfaces/ITokenService.cs b/src/Orbit.Domain/Interfaces/ITokenService.cs index 257475a0..298512d9 100644 --- a/src/Orbit.Domain/Interfaces/ITokenService.cs +++ b/src/Orbit.Domain/Interfaces/ITokenService.cs @@ -2,5 +2,5 @@ namespace Orbit.Domain.Interfaces; public interface ITokenService { - string GenerateToken(Guid userId, string email, bool isAdmin); + string GenerateToken(Guid userId, string email); } diff --git a/src/Orbit.Infrastructure/Services/AuthSessionService.cs b/src/Orbit.Infrastructure/Services/AuthSessionService.cs index 7c91b05c..88f194a1 100644 --- a/src/Orbit.Infrastructure/Services/AuthSessionService.cs +++ b/src/Orbit.Infrastructure/Services/AuthSessionService.cs @@ -24,7 +24,7 @@ public class AuthSessionService( ? nowUtc.AddDays(_jwtSettings.RefreshExpiryDays.Value) : null; - public async Task> CreateSessionAsync(Guid userId, string email, bool isAdmin, CancellationToken cancellationToken = default) + public async Task> CreateSessionAsync(Guid userId, string email, CancellationToken cancellationToken = default) { var refreshToken = GenerateRefreshToken(); var nowUtc = DateTime.UtcNow; @@ -40,7 +40,7 @@ public async Task> CreateSessionAsync(Guid userId, string await unitOfWork.SaveChangesAsync(cancellationToken); return Result.Success(new SessionTokens( - tokenService.GenerateToken(userId, email, isAdmin), + tokenService.GenerateToken(userId, email), refreshToken)); } @@ -69,7 +69,7 @@ public async Task> RefreshSessionAsync(string refreshToken await unitOfWork.SaveChangesAsync(cancellationToken); return Result.Success(new SessionTokens( - tokenService.GenerateToken(user.Id, user.Email, user.IsAdmin), + tokenService.GenerateToken(user.Id, user.Email), newRefreshToken)); } diff --git a/src/Orbit.Infrastructure/Services/JwtTokenService.cs b/src/Orbit.Infrastructure/Services/JwtTokenService.cs index ce0d6d1e..028b0ece 100644 --- a/src/Orbit.Infrastructure/Services/JwtTokenService.cs +++ b/src/Orbit.Infrastructure/Services/JwtTokenService.cs @@ -13,7 +13,7 @@ public class JwtTokenService(IOptions options) : ITokenService { private readonly JwtSettings _settings = options.Value; - public string GenerateToken(Guid userId, string email, bool isAdmin) + public string GenerateToken(Guid userId, string email) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.SecretKey)); @@ -24,9 +24,6 @@ public string GenerateToken(Guid userId, string email, bool isAdmin) new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; - if (isAdmin) - claims.Add(new Claim(AdminPolicy.ClaimType, AdminPolicy.ClaimValue)); - var descriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), diff --git a/src/Orbit.Infrastructure/Services/ResendEmailService.cs b/src/Orbit.Infrastructure/Services/ResendEmailService.cs index 736477b4..00ef61eb 100644 --- a/src/Orbit.Infrastructure/Services/ResendEmailService.cs +++ b/src/Orbit.Infrastructure/Services/ResendEmailService.cs @@ -110,7 +110,10 @@ public async Task SendMarketingEmailAsync( var isPtBr = LocaleHelper.IsPortuguese(language); var footer = MarketingFooterHtml(isPtBr, unsubscribeUrl); var layout = new EmailLayout(LangCode(isPtBr), Preheader: "", footer, LogoUrl, GradientHeader: true); - var html = EmailTemplateRenderer.RenderLayout(layout, bodyHtml); + var readableBody = + "
{bodyHtml}
"; + var html = EmailTemplateRenderer.RenderLayout(layout, readableBody); var payload = new { @@ -131,7 +134,7 @@ public async Task SendMarketingEmailAsync( private static string MarketingFooterHtml(bool isPtBr, string unsubscribeUrl) { const string legalIdentity = - "TL SOFTWARE ENGINEERING LTDA · CNPJ 58.429.979/0001-06 · Av. Nova Independência, 651 — Brooklin Paulista, São Paulo – SP · CEP 04570-001"; + "TL SOFTWARE ENGINEERING LTDA · CNPJ 58.429.979/0001-06 · Av. Nova Independência, 651, Brooklin Paulista, São Paulo/SP · CEP 04570-001"; var (reason, unsubscribeLabel) = isPtBr ? ("Você está recebendo este e-mail porque optou por receber novidades do Orbit.", "Cancelar inscrição") diff --git a/tests/Orbit.Application.Tests/Commands/Auth/AuthCommandHandlerTests.cs b/tests/Orbit.Application.Tests/Commands/Auth/AuthCommandHandlerTests.cs index 6ce3c472..cc92b961 100644 --- a/tests/Orbit.Application.Tests/Commands/Auth/AuthCommandHandlerTests.cs +++ b/tests/Orbit.Application.Tests/Commands/Auth/AuthCommandHandlerTests.cs @@ -61,7 +61,7 @@ public async Task VerifyCode_Valid_ReturnsLoginResponse() var user = User.Create("Test", TestEmail).Value; SetupCacheWithCode("123456"); SetupExistingUser(user); - _authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Result.Success(new SessionTokens("jwt-token", "refresh-token"))); var handler = new VerifyCodeCommandHandler(_cache, _userRepo, _unitOfWork, _authSessionService, _emailService, Substitute.For(), Substitute.For>()); @@ -111,7 +111,7 @@ public async Task VerifyCode_MaxAttempts_ReturnsFailure() public async Task VerifyCode_NewUser_CreatesAccount() { SetupCacheWithCode("123456"); - _authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Result.Success(new SessionTokens("jwt-token", "refresh-token"))); var handler = new VerifyCodeCommandHandler(_cache, _userRepo, _unitOfWork, _authSessionService, _emailService, Substitute.For(), Substitute.For>()); @@ -130,7 +130,7 @@ public async Task VerifyCode_ExistingUser_ReturnsToken() var user = User.Create("Existing", TestEmail).Value; SetupCacheWithCode("123456"); SetupExistingUser(user); - _authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Result.Success(new SessionTokens("jwt-token", "refresh-token"))); var handler = new VerifyCodeCommandHandler(_cache, _userRepo, _unitOfWork, _authSessionService, _emailService, Substitute.For(), Substitute.For>()); diff --git a/tests/Orbit.Application.Tests/Commands/Auth/GoogleAuthCommandHandlerTests.cs b/tests/Orbit.Application.Tests/Commands/Auth/GoogleAuthCommandHandlerTests.cs index de60ecf4..4e5364fb 100644 --- a/tests/Orbit.Application.Tests/Commands/Auth/GoogleAuthCommandHandlerTests.cs +++ b/tests/Orbit.Application.Tests/Commands/Auth/GoogleAuthCommandHandlerTests.cs @@ -41,7 +41,7 @@ public GoogleAuthCommandHandlerTests() Substitute.For(), Substitute.For>()); - _authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Result.Success(new SessionTokens("jwt-token", "refresh-token"))); } diff --git a/tests/Orbit.Application.Tests/Commands/Auth/SendCodeCommandHandlerTests.cs b/tests/Orbit.Application.Tests/Commands/Auth/SendCodeCommandHandlerTests.cs index 8ea0a121..6edc9cfe 100644 --- a/tests/Orbit.Application.Tests/Commands/Auth/SendCodeCommandHandlerTests.cs +++ b/tests/Orbit.Application.Tests/Commands/Auth/SendCodeCommandHandlerTests.cs @@ -153,7 +153,7 @@ private VerifyCodeCommandHandler BuildVerifyHandler() var authSessionService = Substitute.For(); var mediator = Substitute.For(); - authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Result.Success(new SessionTokens("jwt-token", "refresh-token"))); return new VerifyCodeCommandHandler( diff --git a/tests/Orbit.Application.Tests/Commands/Auth/VerifyCodeCommandHandlerTests.cs b/tests/Orbit.Application.Tests/Commands/Auth/VerifyCodeCommandHandlerTests.cs index f44c7faf..0726843e 100644 --- a/tests/Orbit.Application.Tests/Commands/Auth/VerifyCodeCommandHandlerTests.cs +++ b/tests/Orbit.Application.Tests/Commands/Auth/VerifyCodeCommandHandlerTests.cs @@ -33,7 +33,7 @@ public VerifyCodeCommandHandlerTests() _cache, _userRepo, _unitOfWork, _authSessionService, _emailService, _mediator, Substitute.For>()); - _authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + _authSessionService.CreateSessionAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Result.Success(new SessionTokens("jwt-token", "refresh-token"))); } diff --git a/tests/Orbit.Application.Tests/Services/AuthSessionServiceTests.cs b/tests/Orbit.Application.Tests/Services/AuthSessionServiceTests.cs index 41e1568d..f37fe7de 100644 --- a/tests/Orbit.Application.Tests/Services/AuthSessionServiceTests.cs +++ b/tests/Orbit.Application.Tests/Services/AuthSessionServiceTests.cs @@ -66,14 +66,14 @@ public AuthSessionServiceTests() .Returns(_user); _tokenService - .GenerateToken(Arg.Any(), Arg.Any(), Arg.Any()) + .GenerateToken(Arg.Any(), Arg.Any()) .Returns("access-token-1", "access-token-2", "access-token-3"); } [Fact] public async Task CreateSessionAsync_WithNullRefreshExpiry_CreatesNonExpiringSession() { - var result = await _service.CreateSessionAsync(_user.Id, _user.Email, false, CancellationToken.None); + var result = await _service.CreateSessionAsync(_user.Id, _user.Email, CancellationToken.None); result.IsSuccess.Should().BeTrue(); result.Value.AccessToken.Should().Be("access-token-1"); @@ -86,7 +86,7 @@ public async Task CreateSessionAsync_WithNullRefreshExpiry_CreatesNonExpiringSes [Fact] public async Task RefreshSessionAsync_WithNonExpiringSession_RotatesRefreshTokenWithoutAddingExpiry() { - var createResult = await _service.CreateSessionAsync(_user.Id, _user.Email, false, CancellationToken.None); + var createResult = await _service.CreateSessionAsync(_user.Id, _user.Email, CancellationToken.None); var originalTokenHash = _storedSession!.TokenHash; var originalLastUsedAtUtc = _storedSession.LastUsedAtUtc; @@ -106,7 +106,7 @@ public async Task RefreshSessionAsync_WithNonExpiringSession_RotatesRefreshToken [Fact] public async Task RevokeSessionAsync_RevokesStoredSession() { - var createResult = await _service.CreateSessionAsync(_user.Id, _user.Email, false, CancellationToken.None); + var createResult = await _service.CreateSessionAsync(_user.Id, _user.Email, CancellationToken.None); var revokeResult = await _service.RevokeSessionAsync(createResult.Value.RefreshToken, CancellationToken.None); @@ -117,7 +117,7 @@ public async Task RevokeSessionAsync_RevokesStoredSession() [Fact] public async Task RefreshSessionAsync_WithRevokedSession_ReturnsFailure() { - var createResult = await _service.CreateSessionAsync(_user.Id, _user.Email, false, CancellationToken.None); + var createResult = await _service.CreateSessionAsync(_user.Id, _user.Email, CancellationToken.None); _storedSession!.Revoke(DateTime.UtcNow); var refreshResult = await _service.RefreshSessionAsync(createResult.Value.RefreshToken, CancellationToken.None); diff --git a/tests/Orbit.Infrastructure.Tests/Authorization/AdminAuthorizationPolicyTests.cs b/tests/Orbit.Infrastructure.Tests/Authorization/AdminAuthorizationPolicyTests.cs index 1a48c1fd..8d00dc31 100644 --- a/tests/Orbit.Infrastructure.Tests/Authorization/AdminAuthorizationPolicyTests.cs +++ b/tests/Orbit.Infrastructure.Tests/Authorization/AdminAuthorizationPolicyTests.cs @@ -1,46 +1,75 @@ +using System.Linq.Expressions; using System.Security.Claims; using FluentAssertions; using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.DependencyInjection; -using Orbit.Application.Common; +using NSubstitute; +using Orbit.Api.Authorization; +using Orbit.Domain.Entities; +using Orbit.Domain.Interfaces; namespace Orbit.Infrastructure.Tests.Authorization; public class AdminAuthorizationPolicyTests { - private static IAuthorizationService BuildAuthorizationService() + private readonly IGenericRepository _userRepository = Substitute.For>(); + private readonly AdminAuthorizationHandler _handler; + + public AdminAuthorizationPolicyTests() + { + _handler = new AdminAuthorizationHandler(_userRepository); + } + + [Fact] + public async Task AdminUser_Succeeds() { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddAuthorization(options => - options.AddPolicy(AdminPolicy.Name, policy => - policy.RequireClaim(AdminPolicy.ClaimType, AdminPolicy.ClaimValue))); + _userRepository.AnyAsync(Arg.Any>>(), Arg.Any()) + .Returns(true); + var context = ContextFor(Principal(new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()))); - return services.BuildServiceProvider().GetRequiredService(); + await _handler.HandleAsync(context); + + context.HasSucceeded.Should().BeTrue(); } - private static ClaimsPrincipal Principal(params Claim[] claims) => - new(new ClaimsIdentity(claims, authenticationType: "Test")); + [Fact] + public async Task AuthenticatedNonAdmin_DoesNotSucceed() + { + _userRepository.AnyAsync(Arg.Any>>(), Arg.Any()) + .Returns(false); + var context = ContextFor(Principal(new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()))); + + await _handler.HandleAsync(context); + + context.HasSucceeded.Should().BeFalse(); + } [Fact] - public async Task AdminClaim_IsAllowed() + public async Task MissingNameIdentifier_DoesNotSucceed_AndSkipsRepository() { - var principal = Principal( - new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()), - new Claim(AdminPolicy.ClaimType, AdminPolicy.ClaimValue)); + var context = ContextFor(Principal()); - var result = await BuildAuthorizationService().AuthorizeAsync(principal, resource: null, AdminPolicy.Name); + await _handler.HandleAsync(context); - result.Succeeded.Should().BeTrue(); + context.HasSucceeded.Should().BeFalse(); + await _userRepository.DidNotReceive() + .AnyAsync(Arg.Any>>(), Arg.Any()); } [Fact] - public async Task AuthenticatedNonAdmin_IsDenied() + public async Task UnparseableNameIdentifier_DoesNotSucceed_AndSkipsRepository() { - var principal = Principal(new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString())); + var context = ContextFor(Principal(new Claim(ClaimTypes.NameIdentifier, "not-a-guid"))); - var result = await BuildAuthorizationService().AuthorizeAsync(principal, resource: null, AdminPolicy.Name); + await _handler.HandleAsync(context); - result.Succeeded.Should().BeFalse(); + context.HasSucceeded.Should().BeFalse(); + await _userRepository.DidNotReceive() + .AnyAsync(Arg.Any>>(), Arg.Any()); } + + private static ClaimsPrincipal Principal(params Claim[] claims) => + new(new ClaimsIdentity(claims, authenticationType: "Test")); + + private static AuthorizationHandlerContext ContextFor(ClaimsPrincipal principal) => + new([new AdminRequirement()], principal, resource: null); } diff --git a/tests/Orbit.Infrastructure.Tests/Services/AuthSessionServiceTests.cs b/tests/Orbit.Infrastructure.Tests/Services/AuthSessionServiceTests.cs index 1a89e2d5..c85c060c 100644 --- a/tests/Orbit.Infrastructure.Tests/Services/AuthSessionServiceTests.cs +++ b/tests/Orbit.Infrastructure.Tests/Services/AuthSessionServiceTests.cs @@ -20,7 +20,7 @@ public class AuthSessionServiceTests public AuthSessionServiceTests() { - _tokenService.GenerateToken(Arg.Any(), Arg.Any(), Arg.Any()).Returns("access-token"); + _tokenService.GenerateToken(Arg.Any(), Arg.Any()).Returns("access-token"); _sut = new AuthSessionService( _userSessionRepository, @@ -42,7 +42,7 @@ public async Task CreateSessionAsync_AddsPersistedSessionAndReturnsTokens() { var userId = Guid.NewGuid(); - var result = await _sut.CreateSessionAsync(userId, "thomas@test.com", false, CancellationToken.None); + var result = await _sut.CreateSessionAsync(userId, "thomas@test.com", CancellationToken.None); result.IsSuccess.Should().BeTrue(); result.Value.AccessToken.Should().Be("access-token"); @@ -59,7 +59,7 @@ await _userSessionRepository.AddAsync( Arg.Do(session => captured = session), Arg.Any()); - await _sut.CreateSessionAsync(Guid.NewGuid(), "thomas@test.com", false, CancellationToken.None); + await _sut.CreateSessionAsync(Guid.NewGuid(), "thomas@test.com", CancellationToken.None); captured.Should().NotBeNull(); captured!.ExpiresAtUtc.Should().NotBeNull(); diff --git a/tests/Orbit.Infrastructure.Tests/Services/JwtTokenServiceTests.cs b/tests/Orbit.Infrastructure.Tests/Services/JwtTokenServiceTests.cs index 7507161c..3e6f4a8d 100644 --- a/tests/Orbit.Infrastructure.Tests/Services/JwtTokenServiceTests.cs +++ b/tests/Orbit.Infrastructure.Tests/Services/JwtTokenServiceTests.cs @@ -3,7 +3,6 @@ using FluentAssertions; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; -using Orbit.Application.Common; using Orbit.Infrastructure.Configuration; using Orbit.Infrastructure.Services; @@ -34,7 +33,7 @@ public void GenerateToken_ReturnsNonEmptyString() var userId = Guid.NewGuid(); var email = "test@example.com"; - var token = _sut.GenerateToken(userId, email, false); + var token = _sut.GenerateToken(userId, email); token.Should().NotBeNullOrWhiteSpace(); } @@ -45,7 +44,7 @@ public void GenerateToken_ContainsUserIdClaim() var userId = Guid.NewGuid(); var email = "test@example.com"; - var token = _sut.GenerateToken(userId, email, false); + var token = _sut.GenerateToken(userId, email); var handler = new JwtSecurityTokenHandler(); var jwt = handler.ReadJwtToken(token); @@ -61,7 +60,7 @@ public void GenerateToken_ContainsEmailClaim() var userId = Guid.NewGuid(); var email = "user@orbit.test"; - var token = _sut.GenerateToken(userId, email, false); + var token = _sut.GenerateToken(userId, email); var handler = new JwtSecurityTokenHandler(); var jwt = handler.ReadJwtToken(token); @@ -72,24 +71,15 @@ public void GenerateToken_ContainsEmailClaim() } [Fact] - public void GenerateToken_WhenAdmin_ContainsAdminClaim() + public void GenerateToken_OmitsAdminClaim() { - var token = _sut.GenerateToken(Guid.NewGuid(), "admin@orbit.test", isAdmin: true); + var token = _sut.GenerateToken(Guid.NewGuid(), "user@orbit.test"); var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token); - jwt.Claims.Should().Contain(c => - c.Type == AdminPolicy.ClaimType && c.Value == AdminPolicy.ClaimValue); - } - - [Fact] - public void GenerateToken_WhenNotAdmin_OmitsAdminClaim() - { - var token = _sut.GenerateToken(Guid.NewGuid(), "user@orbit.test", isAdmin: false); - - var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token); - - jwt.Claims.Should().NotContain(c => c.Type == AdminPolicy.ClaimType); + jwt.Claims.Should().NotContain(c => + c.Type.Contains("admin", StringComparison.OrdinalIgnoreCase) + || c.Value.Contains("admin", StringComparison.OrdinalIgnoreCase)); } [Fact] @@ -99,7 +89,7 @@ public void GenerateToken_SetsCorrectExpiry() var email = "test@example.com"; var beforeGeneration = DateTime.UtcNow; - var token = _sut.GenerateToken(userId, email, false); + var token = _sut.GenerateToken(userId, email); var handler = new JwtSecurityTokenHandler(); var jwt = handler.ReadJwtToken(token);