proposal-system/api/tests/ProposalSystem.Tests/Controllers/AuthControllerRoleSyncTests.cs
Adam Moussa 9e579f84e2
fix(web,api): pin nanoid and sanitize auth logs (SEC-29) (#312)
* fix: bump nanoid to 3.3.16 and postcss to 8.5.26

Bump nanoid from 3.3.16 to 3.3.18 in web/
Bump postcss from 8.5.25 to 8.5.26 in web/

Closes [Dependabot 47] (https://github.com/Sea-Haven-Industries/proposal-system/security/dependabot/47)

* fix(api): sanitize request path in internal API key logs (SEC-29)

Strip CR/LF from Request.Path before logging invalid-key and disallowed-path warnings so CodeQL alerts 4 and 5 close without changing 401/403 behavior.

* fix(api): log user id instead of email on cognito role sync (SEC-29)

Keep AuthResponse.Email unchanged so CodeQL alert 1 closes without altering the callback payload.

* fix(api): use sanitized path on both internal key logs (SEC-29)

The 401 branch referenced an out-of-scope identifier and the 403 branch skipped SanitizeForLog. Cover newline-in-path logs and Cognito role-sync user-id logging with tests.
2026-08-20 12:38:29 -04:00

85 lines
2.8 KiB
C#

using FluentAssertions;
using Microsoft.Extensions.Configuration;
using NSubstitute;
using ProposalSystem.Api.Controllers;
using ProposalSystem.Domain.Entities;
using ProposalSystem.Tests.Helpers;
using Xunit;
namespace ProposalSystem.Tests.Controllers;
public class AuthControllerRoleSyncTests
{
[Fact(DisplayName = "SEC-29: Cognito role sync logs user id, not email")]
public async Task RoleChange_LogsUserIdNotEmail()
{
var email = "pii@example.com";
var user = new User
{
Id = Guid.NewGuid(),
CognitoSub = "sub-role-sync",
Email = email,
DisplayName = "Test User",
Role = UserRole.Dispatcher,
IsActive = true,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow,
};
var (controller, db, logger) = CreateController(user);
var synced = await controller.SyncCognitoUserAsync(
user.CognitoSub, email, user.DisplayName, UserRole.Admin, CancellationToken.None);
synced.Id.Should().Be(user.Id);
synced.Email.Should().Be(email);
synced.Role.Should().Be(UserRole.Admin);
logger.Messages.Should().ContainSingle();
logger.Messages[0].Should().Contain(user.Id.ToString());
logger.Messages[0].Should().NotContain(email);
logger.Messages[0].Should().Contain(UserRole.Dispatcher.ToString());
logger.Messages[0].Should().Contain(UserRole.Admin.ToString());
db.Dispose();
}
[Fact(DisplayName = "SEC-29: Cognito role sync does not log when role is unchanged")]
public async Task UnchangedRole_DoesNotLog()
{
var user = new User
{
Id = Guid.NewGuid(),
CognitoSub = "sub-unchanged",
Email = "pii@example.com",
DisplayName = "Test User",
Role = UserRole.Admin,
IsActive = true,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow,
};
var (controller, db, logger) = CreateController(user);
await controller.SyncCognitoUserAsync(
user.CognitoSub, user.Email, user.DisplayName, UserRole.Admin, CancellationToken.None);
logger.Messages.Should().BeEmpty();
db.Dispose();
}
private static (AuthController Controller, Infrastructure.Data.ProposalDbContext Db, CapturingLogger<AuthController> Logger)
CreateController(User seed)
{
var db = DbContextFactory.Create();
db.Users.Add(seed);
db.SaveChanges();
var logger = new CapturingLogger<AuthController>();
var controller = new AuthController(
db,
Substitute.For<IHttpClientFactory>(),
new ConfigurationBuilder().Build(),
logger);
return (controller, db, logger);
}
}