From 8a164c33974222ac4281db49f34e4013c2d377f1 Mon Sep 17 00:00:00 2001 From: meoacgx Date: Sat, 20 Dec 2025 05:13:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E7=BB=91=E5=AE=9A/?= =?UTF-8?q?=E6=8D=A2=E7=BB=912FA=E6=89=BE=E5=9B=9E=E9=82=AE=E7=AE=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Telegram/AccountTelegramToolsService.cs | 185 ++++++++++ .../ChangeTwoFactorRecoveryEmailDialog.razor | 328 ++++++++++++++++++ .../Components/Pages/Accounts.razor | 16 + 3 files changed, 529 insertions(+) create mode 100644 src/TelegramPanel.Web/Components/Dialogs/ChangeTwoFactorRecoveryEmailDialog.razor diff --git a/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs b/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs index 567cd04..309d33b 100644 --- a/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs +++ b/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using System.Net.Mail; using TelegramPanel.Core.Interfaces; using TelegramPanel.Core.Models; using TelegramPanel.Data.Entities; @@ -326,6 +327,190 @@ public class AccountTelegramToolsService } } + /// + /// 获取两步验证找回邮箱状态(是否已绑定、是否存在待确认的邮箱)。 + /// + public async Task<(bool Success, string? Error, bool HasTwoFactorPassword, bool HasRecoveryEmail, string? UnconfirmedEmailPattern)> + GetTwoFactorRecoveryEmailStatusAsync(int accountId, CancellationToken cancellationToken = default) + { + try + { + var client = await GetOrCreateConnectedClientAsync(accountId, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + var pwd = await client.Account_GetPassword(); + var hasPassword = pwd.current_algo != null; + var hasRecoveryEmail = pwd.flags.HasFlag(TL.Account_Password.Flags.has_recovery); + var unconfirmed = pwd.flags.HasFlag(TL.Account_Password.Flags.has_email_unconfirmed_pattern) + ? (pwd.email_unconfirmed_pattern ?? "").Trim() + : null; + + if (string.IsNullOrWhiteSpace(unconfirmed)) + unconfirmed = null; + + return (true, null, hasPassword, hasRecoveryEmail, unconfirmed); + } + catch (Exception ex) + { + var (summary, details) = MapTelegramException(ex); + var msg = string.IsNullOrWhiteSpace(details) ? summary : $"{summary}:{details}"; + return (false, msg, false, false, null); + } + } + + /// + /// 绑定/换绑两步验证找回邮箱(会发送验证码到邮箱,需调用 ConfirmTwoFactorRecoveryEmailAsync 确认)。 + /// + public async Task<(bool Success, string? Error, string? EmailPattern)> SetTwoFactorRecoveryEmailAsync( + int accountId, + string? currentPassword, + string email, + CancellationToken cancellationToken = default) + { + try + { + email = (email ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(email)) + return (false, "邮箱不能为空", null); + + try + { + _ = new MailAddress(email); + } + catch + { + return (false, "邮箱格式不正确", null); + } + + currentPassword = (currentPassword ?? string.Empty).Trim(); + + var client = await GetOrCreateConnectedClientAsync(accountId, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + var pwd = await client.Account_GetPassword(); + cancellationToken.ThrowIfCancellationRequested(); + + if (pwd.current_algo == null) + return (false, "该账号未开启两步验证,无法绑定找回邮箱,请先设置二级密码", null); + + if (string.IsNullOrWhiteSpace(currentPassword)) + return (false, "请填写原二级密码", null); + + var oldCheck = await WTelegram.Client.InputCheckPassword(pwd, currentPassword); + + var settings = new TL.Account_PasswordInputSettings + { + flags = TL.Account_PasswordInputSettings.Flags.has_email, + email = email + }; + + await client.Account_UpdatePasswordSettings(oldCheck, settings); + + // 更新后可通过 getPassword 获取“待确认邮箱”掩码信息 + var after = await client.Account_GetPassword(); + var pattern = after.flags.HasFlag(TL.Account_Password.Flags.has_email_unconfirmed_pattern) + ? (after.email_unconfirmed_pattern ?? "").Trim() + : null; + + if (string.IsNullOrWhiteSpace(pattern)) + pattern = null; + + return (true, null, pattern); + } + catch (Exception ex) + { + var (summary, details) = MapTelegramException(ex); + var msg = string.IsNullOrWhiteSpace(details) ? summary : $"{summary}:{details}"; + return (false, msg, null); + } + } + + /// + /// 确认两步验证找回邮箱验证码。 + /// + public async Task<(bool Success, string? Error)> ConfirmTwoFactorRecoveryEmailAsync( + int accountId, + string code, + CancellationToken cancellationToken = default) + { + try + { + code = (code ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(code)) + return (false, "验证码不能为空"); + + var client = await GetOrCreateConnectedClientAsync(accountId, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + _ = await client.Account_ConfirmPasswordEmail(code); + return (true, null); + } + catch (Exception ex) + { + var (summary, details) = MapTelegramException(ex); + var msg = string.IsNullOrWhiteSpace(details) ? summary : $"{summary}:{details}"; + return (false, msg); + } + } + + /// + /// 重发两步验证找回邮箱验证码(需要先设置邮箱)。 + /// + public async Task<(bool Success, string? Error, string? EmailPattern, int? CodeLength)> ResendTwoFactorRecoveryEmailAsync( + int accountId, + CancellationToken cancellationToken = default) + { + try + { + var client = await GetOrCreateConnectedClientAsync(accountId, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + var ok = await client.Account_ResendPasswordEmail(); + if (!ok) + return (false, "重发失败", null, null); + + var pwd = await client.Account_GetPassword(); + var pattern = pwd.flags.HasFlag(TL.Account_Password.Flags.has_email_unconfirmed_pattern) + ? (pwd.email_unconfirmed_pattern ?? "").Trim() + : null; + + if (string.IsNullOrWhiteSpace(pattern)) + pattern = null; + + // 该 API 不返回验证码长度,仅返回邮箱掩码信息 + return (true, null, pattern, null); + } + catch (Exception ex) + { + var (summary, details) = MapTelegramException(ex); + var msg = string.IsNullOrWhiteSpace(details) ? summary : $"{summary}:{details}"; + return (false, msg, null, null); + } + } + + /// + /// 取消待确认的找回邮箱验证码。 + /// + public async Task<(bool Success, string? Error)> CancelTwoFactorRecoveryEmailAsync( + int accountId, + CancellationToken cancellationToken = default) + { + try + { + var client = await GetOrCreateConnectedClientAsync(accountId, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + + _ = await client.Account_CancelPasswordEmail(); + return (true, null); + } + catch (Exception ex) + { + var (summary, details) = MapTelegramException(ex); + var msg = string.IsNullOrWhiteSpace(details) ? summary : $"{summary}:{details}"; + return (false, msg); + } + } + public async Task KickAuthorizationAsync(int accountId, long authorizationHash) { var client = await GetOrCreateConnectedClientAsync(accountId); diff --git a/src/TelegramPanel.Web/Components/Dialogs/ChangeTwoFactorRecoveryEmailDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/ChangeTwoFactorRecoveryEmailDialog.razor new file mode 100644 index 0000000..067f7ab --- /dev/null +++ b/src/TelegramPanel.Web/Components/Dialogs/ChangeTwoFactorRecoveryEmailDialog.razor @@ -0,0 +1,328 @@ +@namespace TelegramPanel.Web.Components.Dialogs +@inject AccountManagementService AccountManagement +@inject TelegramPanel.Core.Services.Telegram.AccountTelegramToolsService AccountTelegramTools +@inject ISnackbar Snackbar +@inject IDialogService DialogService +@using TelegramPanel.Data.Entities + + + + + + 这里绑定的是 Telegram 两步验证(2FA)的“找回邮箱”。修改后会向新邮箱发送验证码,需要输入验证码确认。 + + + @if (!loaded) + { + + } + else if (account == null) + { + 账号不存在 + } + else + { + 账号:@account.Phone + + + + @(hasTwoFactorPassword ? "已开启2FA" : "未开启2FA") + + + @(hasRecoveryEmail ? "已绑定找回邮箱" : "未绑定找回邮箱") + + @if (!string.IsNullOrWhiteSpace(unconfirmedEmailPattern)) + { + 待确认:@unconfirmedEmailPattern + } + + + @if (!hasTwoFactorPassword) + { + + 该账号未开启两步验证,无法绑定找回邮箱。请先设置二级密码。 + + } + else if (step == Step.SetEmail) + { + + + + } + else + { + + + + + 重发验证码 + + + 取消验证 + + + } + } + + + + 关闭 + + @if (loaded && account != null && !hasTwoFactorPassword) + { + + 去设置二级密码 + + } + else if (loaded && account != null && hasTwoFactorPassword) + { + @if (step == Step.SetEmail) + { + + @if (running) + { + + 发送中... + } + else + { + 发送验证码 + } + + } + else + { + + @if (running) + { + + 确认中... + } + else + { + 确认绑定 + } + + } + } + + + +@code +{ + [CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!; + [Parameter] public int AccountId { get; set; } + + private Account? account; + private bool loaded; + private bool running; + + private bool hasTwoFactorPassword; + private bool hasRecoveryEmail; + private string? unconfirmedEmailPattern; + + private Step step = Step.SetEmail; + + private string currentPassword = ""; + private string email = ""; + private string code = ""; + + protected override async Task OnInitializedAsync() + { + await ReloadStatus(); + } + + private async Task ReloadStatus() + { + loaded = false; + try + { + var all = await AccountManagement.GetAllAccountsAsync(); + account = all.FirstOrDefault(a => a.Id == AccountId); + + if (account != null) + { + var (ok, err, hasPwd, hasEmail, unconfirmed) = await AccountTelegramTools.GetTwoFactorRecoveryEmailStatusAsync(AccountId); + if (!ok) + { + Snackbar.Add($"获取2FA邮箱状态失败:{err}", Severity.Warning); + } + hasTwoFactorPassword = hasPwd; + hasRecoveryEmail = hasEmail; + unconfirmedEmailPattern = unconfirmed; + + step = string.IsNullOrWhiteSpace(unconfirmedEmailPattern) ? Step.SetEmail : Step.Confirm; + } + } + finally + { + loaded = true; + } + } + + private async Task SendCode() + { + if (running) + return; + + if (account == null) + return; + + if (string.IsNullOrWhiteSpace(currentPassword)) + { + Snackbar.Add("请填写原二级密码", Severity.Warning); + return; + } + + if (string.IsNullOrWhiteSpace(email)) + { + Snackbar.Add("请填写新找回邮箱", Severity.Warning); + return; + } + + bool? confirm = await DialogService.ShowMessageBox( + "确认发送验证码", + "将向新邮箱发送验证码,用于绑定/换绑找回邮箱。是否继续?", + yesText: "继续", cancelText: "取消"); + + if (confirm != true) + return; + + running = true; + try + { + var (ok, err, pattern) = await AccountTelegramTools.SetTwoFactorRecoveryEmailAsync( + accountId: AccountId, + currentPassword: currentPassword, + email: email, + cancellationToken: CancellationToken.None); + + if (!ok) + { + Snackbar.Add($"发送失败:{err}", Severity.Error); + return; + } + + unconfirmedEmailPattern = pattern; + step = Step.Confirm; + code = ""; + Snackbar.Add("验证码已发送,请到邮箱中查看并输入验证码确认。", Severity.Success); + } + finally + { + running = false; + } + } + + private async Task Confirm() + { + if (running) + return; + + if (string.IsNullOrWhiteSpace(code)) + { + Snackbar.Add("请填写邮箱验证码", Severity.Warning); + return; + } + + running = true; + try + { + var (ok, err) = await AccountTelegramTools.ConfirmTwoFactorRecoveryEmailAsync(AccountId, code, CancellationToken.None); + if (!ok) + { + Snackbar.Add($"确认失败:{err}", Severity.Error); + return; + } + + Snackbar.Add("找回邮箱已确认绑定。", Severity.Success); + await ReloadStatus(); + } + finally + { + running = false; + } + } + + private async Task Resend() + { + if (running) + return; + + running = true; + try + { + var (ok, err, pattern, _) = await AccountTelegramTools.ResendTwoFactorRecoveryEmailAsync(AccountId, CancellationToken.None); + if (!ok) + { + Snackbar.Add($"重发失败:{err}", Severity.Error); + return; + } + + unconfirmedEmailPattern = pattern ?? unconfirmedEmailPattern; + Snackbar.Add("验证码已重发。", Severity.Success); + } + finally + { + running = false; + } + } + + private async Task CancelVerification() + { + if (running) + return; + + bool? confirm = await DialogService.ShowMessageBox( + "取消验证", + "将取消当前待确认的邮箱验证码。是否继续?", + yesText: "继续", cancelText: "取消"); + + if (confirm != true) + return; + + running = true; + try + { + var (ok, err) = await AccountTelegramTools.CancelTwoFactorRecoveryEmailAsync(AccountId, CancellationToken.None); + if (!ok) + { + Snackbar.Add($"取消失败:{err}", Severity.Error); + return; + } + + Snackbar.Add("已取消验证。", Severity.Success); + await ReloadStatus(); + } + finally + { + running = false; + } + } + + private void Close() => MudDialog.Close(); + + private async Task OpenPasswordDialog() + { + if (account == null) + return; + + var parameters = new DialogParameters + { + ["AccountIds"] = new List { account.Id } + }; + var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true }; + DialogService.Show("设置二级密码", parameters, options); + MudDialog.Close(); + await Task.CompletedTask; + } + + private enum Step + { + SetEmail, + Confirm + } +} diff --git a/src/TelegramPanel.Web/Components/Pages/Accounts.razor b/src/TelegramPanel.Web/Components/Pages/Accounts.razor index 2b080de..278e533 100644 --- a/src/TelegramPanel.Web/Components/Pages/Accounts.razor +++ b/src/TelegramPanel.Web/Components/Pages/Accounts.razor @@ -125,6 +125,8 @@ Disabled="@loading" OnClick="@(() => OpenDevices(context))" Title="在线设备" /> + ("绑定/换绑找回邮箱", parameters, options); + await Task.CompletedTask; + } + private async Task OpenBatchChangeTwoFactorPassword() { if (loading)