Add Telegram bot integration and enhance user management features
- Introduced Telegram.Bot package for bot functionality. - Updated user management to include Telegram linking and blocking features. - Enhanced activation request handling with notifications via Telegram. - Added new database entities for Telegram link tokens and login requests. - Implemented traffic synchronization for client stats in the XuiPanelGateway. - Updated application structure to support new test projects and improved dependency injection for Telegram services.
This commit is contained in:
@@ -39,6 +39,9 @@ internal sealed class IdentityService(UserManager<AppUser> userManager, SignInMa
|
||||
if (user is null)
|
||||
return Result.Failure<AuthenticatedUser>(AuthErrors.InvalidCredentials);
|
||||
|
||||
if (user.IsBlocked)
|
||||
return Result.Failure<AuthenticatedUser>(AuthErrors.UserBlocked);
|
||||
|
||||
var checkResult = await signInManager.CheckPasswordSignInAsync(user, password, lockoutOnFailure: true);
|
||||
if (checkResult.IsLockedOut)
|
||||
return Result.Failure<AuthenticatedUser>(AuthErrors.LockedOut);
|
||||
@@ -56,7 +59,7 @@ internal sealed class IdentityService(UserManager<AppUser> userManager, SignInMa
|
||||
return null;
|
||||
|
||||
var role = await GetPrimaryRoleAsync(user);
|
||||
return new CurrentUserProfile(user.Id, user.UserName!, role.Id, role.Name!, user.IsActivated, role.MaxConfigs);
|
||||
return new CurrentUserProfile(user.Id, user.UserName!, role.Id, role.Name!, user.IsActivated, user.IsBlocked, role.MaxConfigs);
|
||||
}
|
||||
|
||||
public async Task<Result> ChangePasswordAsync(Guid userId, string currentPassword, string newPassword, CancellationToken cancellationToken)
|
||||
@@ -118,6 +121,118 @@ internal sealed class IdentityService(UserManager<AppUser> userManager, SignInMa
|
||||
return user?.Id;
|
||||
}
|
||||
|
||||
public async Task<Result> BlockUserAsync(Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await userManager.FindByIdAsync(userId.ToString());
|
||||
if (user is null)
|
||||
return Result.Failure(AuthErrors.Unauthorized);
|
||||
|
||||
user.IsBlocked = true;
|
||||
await userManager.UpdateSecurityStampAsync(user); // гасит уже выданные refresh-токены де-факто — токен привязан к пользователю, не к stamp; отзыв делает вызывающий handler явно
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public async Task<Result> UnblockUserAsync(Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await userManager.FindByIdAsync(userId.ToString());
|
||||
if (user is null)
|
||||
return Result.Failure(AuthErrors.Unauthorized);
|
||||
|
||||
user.IsBlocked = false;
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public async Task<Result> ResetPasswordAsync(Guid userId, string newPassword, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await userManager.FindByIdAsync(userId.ToString());
|
||||
if (user is null)
|
||||
return Result.Failure(AuthErrors.Unauthorized);
|
||||
|
||||
var token = await userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var result = await userManager.ResetPasswordAsync(user, token, newPassword);
|
||||
|
||||
return result.Succeeded
|
||||
? Result.Success()
|
||||
: Result.Failure(Error.Validation(
|
||||
"Auth.PasswordResetFailed", string.Join("; ", result.Errors.Select(e => e.Description))));
|
||||
}
|
||||
|
||||
public async Task<PagedList<UserSummaryDto>> ListUsersAsync(int page, int pageSize, string? search, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = userManager.Users.AsNoTracking();
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
query = query.Where(u => u.UserName!.Contains(search));
|
||||
|
||||
var total = await query.CountAsync(cancellationToken);
|
||||
var users = await query
|
||||
.OrderBy(u => u.UserName)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var items = new List<UserSummaryDto>(users.Count);
|
||||
foreach (var user in users)
|
||||
{
|
||||
var roleName = await GetPrimaryRoleNameAsync(user);
|
||||
items.Add(new UserSummaryDto(user.Id, user.UserName!, roleName, user.IsActivated, user.IsBlocked, user.ActivatedAt));
|
||||
}
|
||||
|
||||
return new PagedList<UserSummaryDto>(items, total, page, pageSize);
|
||||
}
|
||||
|
||||
public async Task<UserStatsDto> GetUserStatsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var total = await userManager.Users.CountAsync(cancellationToken);
|
||||
var activated = await userManager.Users.CountAsync(u => u.IsActivated, cancellationToken);
|
||||
return new UserStatsDto(total, activated);
|
||||
}
|
||||
|
||||
public async Task<Result> LinkTelegramAsync(Guid userId, long telegramUserId, string? telegramUsername, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await userManager.FindByIdAsync(userId.ToString());
|
||||
if (user is null)
|
||||
return Result.Failure(AuthErrors.Unauthorized);
|
||||
|
||||
var alreadyLinked = await userManager.Users.AsNoTracking()
|
||||
.AnyAsync(u => u.TelegramUserId == telegramUserId && u.Id != userId, cancellationToken);
|
||||
if (alreadyLinked)
|
||||
return Result.Failure(Error.Conflict("Telegram.AlreadyLinked", "Этот Telegram уже привязан к другому аккаунту."));
|
||||
|
||||
user.TelegramUserId = telegramUserId;
|
||||
user.TelegramUsername = telegramUsername;
|
||||
user.TelegramLinkedAt = DateTimeOffset.UtcNow;
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public async Task<Result> UnlinkTelegramAsync(Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await userManager.FindByIdAsync(userId.ToString());
|
||||
if (user is null)
|
||||
return Result.Failure(AuthErrors.Unauthorized);
|
||||
|
||||
user.TelegramUserId = null;
|
||||
user.TelegramUsername = null;
|
||||
user.TelegramLinkedAt = null;
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public async Task<Guid?> FindUserIdByTelegramUserIdAsync(long telegramUserId, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await userManager.Users.AsNoTracking()
|
||||
.FirstOrDefaultAsync(u => u.TelegramUserId == telegramUserId, cancellationToken);
|
||||
return user?.Id;
|
||||
}
|
||||
|
||||
public async Task<TelegramLinkInfo> GetTelegramLinkInfoAsync(Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await userManager.FindByIdAsync(userId.ToString());
|
||||
return user?.TelegramUserId is not null
|
||||
? new TelegramLinkInfo(true, user.TelegramUserId, user.TelegramUsername)
|
||||
: new TelegramLinkInfo(false, null, null);
|
||||
}
|
||||
|
||||
private async Task<string> GetPrimaryRoleNameAsync(AppUser user)
|
||||
{
|
||||
var roles = await userManager.GetRolesAsync(user);
|
||||
|
||||
Reference in New Issue
Block a user