Files
tiku-backend.net/Tiku.Infrastructure/Auth/PasswordLifecycle/AuthService.PasswordLifecycle.cs
xiong c497a3ca8d
Some checks failed
ci / release-gate (push) Has been cancelled
清理代码
2026-08-03 12:31:39 +08:00

145 lines
6.4 KiB
C#

using Microsoft.EntityFrameworkCore;
using Tiku.Application.Auth;
using Tiku.Domain.Identity;
using Tiku.Domain.Tenancy;
namespace Tiku.Infrastructure.Auth;
public sealed partial class AuthService
{
public async Task<AuthenticationResult> ChangeRequiredPasswordAsync(
PasswordChangeChallengeRequest request,
CancellationToken cancellationToken = default)
{
var challenge = await FindChallengeAsync(
request.ChallengeToken, AuthChallengePurpose.PasswordChange, cancellationToken);
var user = await userManager.FindByIdAsync(challenge.UserId.ToString())
?? throw new InvalidAuthChallengeException();
var resetToken = await userManager.GeneratePasswordResetTokenAsync(user);
var reset = await userManager.ResetPasswordAsync(user, resetToken, request.NewPassword);
if (!reset.Succeeded) throw new InvalidCredentialsException("invalid_new_password");
user.ForcePasswordChange = false;
var updated = await userManager.UpdateAsync(user);
if (!updated.Succeeded) throw new InvalidOperationException("Unable to clear the password-change requirement.");
await sessionStore.RevokeAllAsync(user.Id, "password_changed", cancellationToken);
await ConsumeChallengeAsync(challenge, cancellationToken);
await AddSecurityAuditAsync(
user.Id, challenge.TenantId, "auth.password.changed", null,
request.IpAddress, request.UserAgent, cancellationToken);
return await CompleteSuccessfulLoginAsync(
challenge.Realm, challenge.TenantId, user, challenge.Provider,
user.Email ?? user.Phone ?? user.Id.ToString(),
request.IpAddress, request.UserAgent, cancellationToken);
}
public async Task<SmsSendResult> RequestPasswordResetAsync(
PasswordResetCodeRequest request,
CancellationToken cancellationToken = default)
{
var phone = SmsCodeHashing.NormalizePhone(request.Phone);
var userId = await dbContext.Users.AsNoTracking()
.Where(user => user.Phone == phone && user.Status == UserStatus.Active)
.Select(user => (Guid?)user.Id)
.SingleOrDefaultAsync(cancellationToken);
var eligible = userId.HasValue && await dbContext.TenantMemberships.AsNoTracking().AnyAsync(
membership => membership.TenantId == request.TenantId && membership.UserId == userId.Value &&
membership.Status == MembershipStatus.Active,
cancellationToken);
if (!eligible) return new SmsSendResult(Guid.NewGuid(), DateTimeOffset.UtcNow.AddMinutes(5));
return await smsVerificationService.CreateCodeAsync(
new SendSmsCodeRequest(
request.TenantId,
phone,
SmsPurpose.ResetPassword,
request.IpAddress,
request.UserAgent,
request.DeviceId),
cancellationToken);
}
public async Task ResetPasswordAsync(
PasswordResetRequest request,
CancellationToken cancellationToken = default)
{
var phone = SmsCodeHashing.NormalizePhone(request.Phone);
var user = await dbContext.Users.SingleOrDefaultAsync(
item => item.Phone == phone && item.Status == UserStatus.Active,
cancellationToken);
if (user is null || !await dbContext.TenantMemberships.AnyAsync(
membership => membership.TenantId == request.TenantId && membership.UserId == user.Id &&
membership.Status == MembershipStatus.Active,
cancellationToken))
throw new InvalidCredentialsException();
await smsVerificationService.VerifyCodeAsync(
request.TenantId,
phone,
SmsPurpose.ResetPassword,
request.Code,
cancellationToken);
var token = await userManager.GeneratePasswordResetTokenAsync(user);
var reset = await userManager.ResetPasswordAsync(user, token, request.NewPassword);
if (!reset.Succeeded) throw new InvalidCredentialsException("invalid_new_password");
user.ForcePasswordChange = false;
var updated = await userManager.UpdateAsync(user);
if (!updated.Succeeded) throw new InvalidOperationException("Unable to finalize the password reset.");
await sessionStore.RevokeAllAsync(user.Id, "password_reset", cancellationToken);
await AddSecurityAuditAsync(
user.Id,
request.TenantId,
"auth.password.reset",
null,
request.IpAddress,
request.UserAgent,
cancellationToken);
}
public async Task<AuthenticationResult> ChangePasswordAsync(
AuthenticatedPasswordChangeRequest request,
CancellationToken cancellationToken = default)
{
var session = await sessionStore.ResolveActiveSessionAsync(
request.SessionId,
request.UserId,
cancellationToken) ?? throw new SessionRevokedException();
var user = await userManager.FindByIdAsync(request.UserId.ToString())
?? throw new InvalidCredentialsException();
var changed = await userManager.ChangePasswordAsync(user, request.CurrentPassword, request.NewPassword);
if (!changed.Succeeded)
{
var currentPasswordInvalid = changed.Errors.Any(error =>
string.Equals(error.Code, "PasswordMismatch", StringComparison.OrdinalIgnoreCase));
throw new InvalidCredentialsException(currentPasswordInvalid
? "invalid_credentials"
: "invalid_new_password");
}
user.ForcePasswordChange = false;
var updated = await userManager.UpdateAsync(user);
if (!updated.Succeeded) throw new InvalidOperationException("Unable to finalize the password change.");
await sessionStore.RevokeAllAsync(user.Id, "password_changed", cancellationToken);
await AddSecurityAuditAsync(
user.Id,
session.TenantId,
"auth.password.changed_authenticated",
null,
request.IpAddress,
request.UserAgent,
cancellationToken);
return await CompleteSuccessfulLoginAsync(
session.Realm,
session.TenantId,
user,
PasswordProvider,
user.Email ?? user.Phone ?? user.Id.ToString(),
request.IpAddress,
request.UserAgent,
cancellationToken);
}
}