feat: strengthen P0 security and operations
This commit is contained in:
70
Tiku.Infrastructure/Auth/AuthAdministrationService.cs
Normal file
70
Tiku.Infrastructure/Auth/AuthAdministrationService.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Domain.Identity;
|
||||
using Tiku.Domain.Operations;
|
||||
using Tiku.Domain.Tenancy;
|
||||
using Tiku.Infrastructure.Persistence;
|
||||
|
||||
namespace Tiku.Infrastructure.Auth;
|
||||
|
||||
internal sealed class AuthAdministrationService(
|
||||
TikuDbContext dbContext,
|
||||
UserManager<User> userManager,
|
||||
IAuthSessionStore sessionStore) : IAuthAdministrationService
|
||||
{
|
||||
public async Task ResetPasswordAsync(
|
||||
AdministrativePasswordResetRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Reason))
|
||||
{
|
||||
throw new InvalidCredentialsException("password_reset_reason_required");
|
||||
}
|
||||
|
||||
var permitted = request.TenantId is { } tenantId
|
||||
? await dbContext.TenantMemberships.AnyAsync(
|
||||
item => item.TenantId == tenantId && item.UserId == request.TargetUserId &&
|
||||
item.Status == MembershipStatus.Active,
|
||||
cancellationToken)
|
||||
: await dbContext.PlatformBackendUserRoles.AnyAsync(
|
||||
item => item.UserId == request.TargetUserId,
|
||||
cancellationToken);
|
||||
if (!permitted)
|
||||
{
|
||||
throw new AuthSessionNotFoundException();
|
||||
}
|
||||
|
||||
var user = await userManager.FindByIdAsync(request.TargetUserId.ToString())
|
||||
?? throw new AuthSessionNotFoundException();
|
||||
var token = await userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var reset = await userManager.ResetPasswordAsync(user, token, request.TemporaryPassword);
|
||||
if (!reset.Succeeded)
|
||||
{
|
||||
throw new InvalidCredentialsException("invalid_new_password");
|
||||
}
|
||||
|
||||
user.ForcePasswordChange = true;
|
||||
var updated = await userManager.UpdateAsync(user);
|
||||
if (!updated.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to require a password change after the administrative reset.");
|
||||
}
|
||||
|
||||
await sessionStore.RevokeAllAsync(user.Id, "administrative_password_reset", cancellationToken);
|
||||
dbContext.AuditLogs.Add(new AuditLog
|
||||
{
|
||||
TenantId = request.TenantId,
|
||||
ActorUserId = request.ActorUserId,
|
||||
Action = "auth.password.reset_by_administrator",
|
||||
TargetType = "user",
|
||||
TargetId = request.TargetUserId.ToString(),
|
||||
Details = System.Text.Json.JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
request.Reason,
|
||||
ForcePasswordChange = true
|
||||
})
|
||||
});
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user