Core3 / React подтверждение по электронной почте не отправлено - PullRequest
0 голосов
/ 10 марта 2020

Этот вопрос относится к проекту core3 / реагирует с внешним поставщиком удостоверений, созданным следующим образом.

dotnet new react --auth Individual --use-local-db --output conf

и измененным для поддержки внешнего поставщика удостоверений. Пакет добавляется

dotnet add package Microsoft.AspNetCore.Authentication.MicrosoftAccount

, а запуск изменяется

services.AddAuthentication()
.AddMicrosoftAccount(options =>
{
    options.ClientId = Configuration["Authentication:Microsoft:ClientId"];
    options.ClientSecret = Configuration["Authentication:Microsoft:ClientSecret"];
    options.CallbackPath = "/signin-microsoft";
})

После следования инструкциям , предоставленным Microsoft , я проверил свою работу, зарегистрировавшись в качестве пользователя. Не было выдано никаких ошибок, но обещанное электронное письмо с подтверждением так и не пришло.

Следуя советам по устранению неполадок в конце инструкций, я установил точку останова в начале метода SendEmailAsyn c моей реализации IEmailSender и повторил упражнение. Точка останова не достигнута.

Если я вручную подтверждаю учетную запись путем обновления базы данных,

  • Я могу войти в систему.
  • Ссылка Забыли пароль занимает Я перехожу на страницу восстановления пароля и, используя это, попадает в точку останова и успешно отправляет электронное письмо для сброса пароля со ссылкой, которая работает.

Очевидно, что моя реализация IEmailSender работает и правильно зарегистрирована. Это не совсем то же самое, что и пример кода, потому что у меня есть собственный сервер Exchange и я не использовал SendGrid, но он успешно отправил электронное письмо для сброса пароля, и я могу повторить это любое количество раз без заминки.

Несмотря на малую вероятность того, что это как-то является причиной проблемы, вот моя реализация

public class SmtpEmailSender : IEmailSender
{
    public SmtpEmailSender(IOptions<SmtpOptions> options)
    {
        this.smtpOptions = options.Value;
    }
    private SmtpOptions smtpOptions { get; }
    public Task SendEmailAsync(string email, string subject, string htmlMessage)
    {
        var smtp = new SmtpClient();
        if (!smtpOptions.ValidateCertificate)
        {
            smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
        }
        smtp.Connect(smtpOptions.Host, smtpOptions.Port, SecureSocketOptions.Auto);
        if (smtpOptions.Authenticate)
        {
            smtp.Authenticate(smtpOptions.Username, smtpOptions.Password);
        }
        var message = new MimeMessage()
        {
            Subject = subject,
            Body = new BodyBuilder() { HtmlBody = htmlMessage }.ToMessageBody()
        };
        message.From.Add(new MailboxAddress(smtpOptions.Sender));
        message.To.Add(new MailboxAddress(email));
        return smtp.SendAsync(FormatOptions.Default, message).ContinueWith(antecedent =>
        {
            smtp.Disconnect(true);
            smtp.Dispose();
        });
    }
}

Регистрация в startup.cs выглядит следующим образом.

            services.AddTransient<IEmailSender, SmtpEmailSender>();
            services.Configure<SmtpOptions>(Configuration.GetSection("SmtpOptions"));

SmptOptions - это просто перетаскиваемые настройки из appsettings. json и вводится в ctor. Очевидно, что этот аспект работает, или электронные письма для сброса пароля не будут работать.

В регистрации не может быть ничего плохого, потому что приложение перестает выдавать сообщение о необходимости прочитать и следовать инструкциям подтверждения учетной записи, которые я связал.

Чтобы увидеть, была ли проблема вызвана каким-то непреднамеренным побочным эффектом моего кода, я создал инструментальную заглушку IEmailSender

public class DummyEmailSender : IEmailSender
{
    private readonly ILogger logger;

    public DummyEmailSender(ILogger<DummyEmailSender> logger)
    {
        this.logger = logger;
    }
    public Task SendEmailAsync(string email, string subject, string htmlMessage)
    {
        logger.LogInformation($"SEND EMAIL\r\nemail={email} \r\nsubject={subject}\r\nhtmlMessage={htmlMessage}\r\n{new StackTrace().ToString().Substring(0,500)}");
        return Task.CompletedTask;
    }
}

Я также обновил регистрацию службы, чтобы соответствовать.

Это самый простой из возможных инструментальных заглушек, и наблюдаемое поведение такое же, оно вызывается при отправке формы забытого пароля и не вызывается при отправке формы подтверждения регистрации.

Кто-нибудь когда-нибудь получал эту ужасную работу? Как?


Непосредственно перед сбоем этот URL https://wone.pdconsec.net/Identity/Account/ExternalLogin?returnUrl=%2Fauthentication%2Flogin&handler=Callback выглядит следующим образом

enter image description here

Проверка найденной страницы кнопка «Зарегистрировать» отправляет форму в /Identity/Account/ExternalLogin?returnUrl=%2Fauthentication%2Flogin&amp;handler=Confirmation

Код для этого доступен в репозитории do tnet. После клонирования репо https://github.com/dotnet/aspnetcore.git я прочитал инструкции по сборке и преуспел в создании предварительного просмотра tnet 5. Затем я запустил clean, прежде чем переключиться на помеченную ветку release/3.1, чтобы создать отладочные пакеты для core3.1, но это не удалось, потому что помеченная ветвь привносит в игру версию msbuild, которая немного устарела, и средство, предложенное в сообщении об ошибке. не похоже на работу. Поскольку мое владение PowerShell слабое (сценарий сборки - PowerShell), я вынужден заниматься проверкой кода. Соответствующий код выглядит следующим образом.

public override async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    // Get the information about the user from the external login provider
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null)
    {
        ErrorMessage = "Error loading external login information during confirmation.";
        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    if (ModelState.IsValid)
    {
        var user = CreateUser();

        await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
        await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);

        var result = await _userManager.CreateAsync(user);
        if (result.Succeeded)
        {
            result = await _userManager.AddLoginAsync(user, info);
            if (result.Succeeded)
            {
                _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);

                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                var callbackUrl = Url.Page(
                        "/Account/ConfirmEmail",
                        pageHandler: null,
                        values: new { area = "Identity", userId = userId, code = code },
                        protocol: Request.Scheme);

                await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                        $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

                // If account confirmation is required, we need to show the link if we don't have a real email sender
                if (_userManager.Options.SignIn.RequireConfirmedAccount)
                {
                    return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
                }

                await _signInManager.SignInAsync(user, isPersistent: false);
                return LocalRedirect(returnUrl);
            }
        }
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    ProviderDisplayName = info.ProviderDisplayName;
    ReturnUrl = returnUrl;
    return Page();
}

Похоже, он должен работать. Что мы знаем?

  • Не выдается никаких необработанных ошибок, оно передается в RegisterConfirmation, которая отправляет сообщение об электронной почте, которая никогда не приходит.
  • CreateUser вызывается и преуспевает. Мы знаем это, потому что пользователь создан в базе данных. Таким образом, он определенно проходит мимо, что означает, что ModelState не равно нулю, а .IsValid истинно.
  • IEmailSender.SendEmailAsyn c фактически не вызывается, несмотря на приведенный выше код.
  • Если result.Succeeded истинно, должно появиться сообщение в журнале, говорящее что-то вроде: «Пользователь создал учетную запись с помощью поставщика учетных записей Microsoft»
  • Перенаправляет на https://localhost:5001/Identity/Account/RegisterConfirmation?Email=accountname@outlook.com

I ' Я вижу сообщения журнала для большинства вещей. Попытка зарегистрироваться во второй раз после первого прохода создает пользователя, но не может отправить электронное письмо, на консоли и в журнале событий появляется предупреждение о DuplicateUserName. Установив подтверждение непосредственно в базе данных, мы можем войти в систему, а затем в интерактивном режиме удалить учетную запись, и для этих действий отображаются журналы.

Но журналы для подтверждения не отображаются. То, что действительно делает мою голову в том, что она затем перенаправляет на https://localhost:5001/Identity/Account/RegisterConfirmation?Email=accountname@outlook.com

Это безумие. Чтобы попасть туда, userManager.AddLoginAsync() должен вернуть true, и следующая строка в этом случае - запись в регистратор о создании учетной записи пользователя.

Это не имеет смысла.

Ответы [ 2 ]

0 голосов
/ 14 марта 2020

Я создал совершенно новый проект и выполнил упражнение. Работает отлично.

Какая разница? Неудачная версия была добавлена ​​в существующий проект, который несколько раз дергался взад-вперед между 3,0 и 3,1 в ходе устранения неполадок CICD. Очевидно, что он поврежден каким-то неочевидным способом, и это не проблема.

Единственная причина, по которой я не удалил весь вопрос, состоит в том, что другие могут упасть в эту дыру.

0 голосов
/ 10 марта 2020

Вы должны отправить подтверждение по электронной почте самостоятельно, оно не отправляется автоматически. После регистрации вашего пользователя:

        string token = await userManager.GenerateEmailConfirmationTokenAsync(user);
        string urltoken = Base64UrlEncoder.Encode(token);

        string link = string.Format(emailOptions.ConfirmationUrl, user.Id, urltoken);
        string body = $"<a href='{link}'>confirm</a>";
        await emailSender.SendEmailAsync(user.Email, "confirmation", body);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...