diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/EmailAuthenticatorController.cs b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/EmailAuthenticatorController.cs index 5eb16d5d9ed..635b8bdf32f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Controllers/EmailAuthenticatorController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Controllers/EmailAuthenticatorController.cs @@ -148,37 +148,6 @@ public async Task ValidateCode(EnableEmailAuthenticatorViewModel return View(nameof(RequestCode), model); } - [HttpPost, Produces("application/json"), AllowAnonymous] - public async Task SendCode() - { - var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); - var errorMessage = S["The email could not be sent. Please attempt to request the code at a later time."]; - - if (user == null) - { - return BadRequest(new - { - success = false, - message = errorMessage.Value, - }); - } - - var settings = (await SiteService.GetSiteSettingsAsync()).As(); - var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultEmailProvider); - - var to = await UserManager.GetEmailAsync(user); - var subject = await GetSubjectAsync(settings, user, code); - var body = await GetBodyAsync(settings, user, code); - var result = await _emailService.SendAsync(to, subject, body); - - return Ok(new - { - success = result.Succeeded, - message = result.Succeeded ? S["A verification code has been sent via email. Please check your email for the code."].Value - : errorMessage.Value, - }); - } - private Task GetSubjectAsync(EmailAuthenticatorLoginSettings settings, IUser user, string code) { var message = string.IsNullOrWhiteSpace(settings.Subject) diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Endpoints/EmailAuthenticator/SendCode.cs b/src/OrchardCore.Modules/OrchardCore.Users/Endpoints/EmailAuthenticator/SendCode.cs new file mode 100644 index 00000000000..461449be49e --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Users/Endpoints/EmailAuthenticator/SendCode.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.IO; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Fluid.Values; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Localization; +using OrchardCore.Email; +using OrchardCore.Liquid; +using OrchardCore.Settings; +using OrchardCore.Users.Models; + +namespace OrchardCore.Users.Endpoints.EmailAuthenticator; + +public static class SendCode +{ + public const string RouteName = "EmailAuthenticatorSendCode"; + + public static IEndpointRouteBuilder AddEmailSendCodeEndpoint(this IEndpointRouteBuilder builder) + { + builder.MapPost("TwoFactor-Authenticator/EmailSendCode", HandleAsync) + .AllowAnonymous() + .WithName(RouteName) + .DisableAntiforgery(); + + return builder; + } + + private static async Task HandleAsync( + SignInManager signInManager, + UserManager userManager, + ISiteService siteService, + IEmailService emailService, + ILiquidTemplateManager liquidTemplateManager, + HtmlEncoder htmlEncoder, + IStringLocalizer S) + { + var user = await signInManager.GetTwoFactorAuthenticationUserAsync(); + var errorMessage = S["The email could not be sent. Please attempt to request the code at a later time."]; + + if (user == null) + { + return TypedResults.BadRequest(new + { + success = false, + message = errorMessage.Value, + }); + } + + var settings = (await siteService.GetSiteSettingsAsync()).As(); + var code = await userManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultEmailProvider); + + var to = await userManager.GetEmailAsync(user); + var subject = await GetSubjectAsync(settings, user, code, liquidTemplateManager, htmlEncoder); + var body = await GetBodyAsync(settings, user, code, liquidTemplateManager, htmlEncoder); + var result = await emailService.SendAsync(to, subject, body); + + return TypedResults.Ok(new + { + success = result.Succeeded, + message = result.Succeeded + ? S["A verification code has been sent via email. Please check your email for the code."].Value + : errorMessage.Value, + }); + } + + private static Task GetSubjectAsync( + EmailAuthenticatorLoginSettings settings, + IUser user, + string code, + ILiquidTemplateManager liquidTemplateManager, + HtmlEncoder htmlEncoder) + { + var message = string.IsNullOrWhiteSpace(settings.Subject) + ? EmailAuthenticatorLoginSettings.DefaultSubject + : settings.Subject; + + return GetContentAsync(message, user, code, liquidTemplateManager, htmlEncoder); + } + + private static Task GetBodyAsync( + EmailAuthenticatorLoginSettings settings, + IUser user, + string code, + ILiquidTemplateManager liquidTemplateManager, + HtmlEncoder htmlEncoder) + { + var message = string.IsNullOrWhiteSpace(settings.Body) + ? EmailAuthenticatorLoginSettings.DefaultBody + : settings.Body; + + return GetContentAsync(message, user, code, liquidTemplateManager, htmlEncoder); + } + + private static async Task GetContentAsync( + string message, + IUser user, + string code, + ILiquidTemplateManager liquidTemplateManager, + HtmlEncoder htmlEncoder) + { + var result = await liquidTemplateManager.RenderHtmlContentAsync(message, htmlEncoder, null, + new Dictionary() + { + ["User"] = new ObjectValue(user), + ["Code"] = new StringValue(code), + }); + + using var writer = new StringWriter(); + result.WriteTo(writer, htmlEncoder); + + return writer.ToString(); + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Endpoints/SmsAuthenticator/SendCode.cs b/src/OrchardCore.Modules/OrchardCore.Users/Endpoints/SmsAuthenticator/SendCode.cs new file mode 100644 index 00000000000..b700b3e99dd --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Users/Endpoints/SmsAuthenticator/SendCode.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using System.IO; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Fluid.Values; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using OrchardCore.Liquid; +using OrchardCore.Settings; +using OrchardCore.Sms; +using OrchardCore.Users.Models; + +namespace OrchardCore.Users.Endpoints.SmsAuthenticator; + +public static class SendCode +{ + public const string RouteName = "SmsAuthenticatorSendCode"; + + public static IEndpointRouteBuilder AddSmsSendCodeEndpoint(this IEndpointRouteBuilder builder) + { + builder.MapPost("TwoFactor-Authenticator/SmsSendCode", HandleAsync) + .AllowAnonymous() + .WithName(RouteName) + .DisableAntiforgery(); + + return builder; + } + + private static async Task HandleAsync( + SignInManager signInManager, + UserManager userManager, + ISiteService siteService, + ISmsService smsService, + IOptions identityOptions, + ILiquidTemplateManager liquidTemplateManager, + HtmlEncoder htmlEncoder, + IStringLocalizer S) + { + var user = await signInManager.GetTwoFactorAuthenticationUserAsync(); + var errorMessage = S["The SMS message could not be sent. Please attempt to request the code at a later time."]; + + if (user == null) + { + return TypedResults.BadRequest(new + { + success = false, + message = errorMessage.Value, + }); + } + + var settings = (await siteService.GetSiteSettingsAsync()).As(); + var code = await userManager.GenerateTwoFactorTokenAsync(user, identityOptions.Value.Tokens.ChangePhoneNumberTokenProvider); + + var message = new SmsMessage() + { + To = await userManager.GetPhoneNumberAsync(user), + Body = await GetBodyAsync(settings, user, code, liquidTemplateManager, htmlEncoder), + }; + + var result = await smsService.SendAsync(message); + + return TypedResults.Ok(new + { + success = result.Succeeded, + message = result.Succeeded ? S["A verification code has been sent to your phone number. Please check your device for the code."].Value + : errorMessage.Value, + }); + } + + private static Task GetBodyAsync( + SmsAuthenticatorLoginSettings settings, + IUser user, + string code, + ILiquidTemplateManager liquidTemplateManager, + HtmlEncoder htmlEncoder) + { + var message = string.IsNullOrWhiteSpace(settings.Body) + ? EmailAuthenticatorLoginSettings.DefaultBody + : settings.Body; + + return GetContentAsync(message, user, code, liquidTemplateManager, htmlEncoder); + } + + private static async Task GetContentAsync( + string message, + IUser user, + string code, + ILiquidTemplateManager liquidTemplateManager, + HtmlEncoder htmlEncoder) + { + var result = await liquidTemplateManager.RenderHtmlContentAsync(message, htmlEncoder, null, + new Dictionary() + { + ["User"] = new ObjectValue(user), + ["Code"] = new StringValue(code), + }); + + using var writer = new StringWriter(); + result.WriteTo(writer, htmlEncoder); + + return writer.ToString(); + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Users/TwoFactorAuthenticationStartup.cs b/src/OrchardCore.Modules/OrchardCore.Users/TwoFactorAuthenticationStartup.cs index fb58a9f264a..eb1c2df28b7 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/TwoFactorAuthenticationStartup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/TwoFactorAuthenticationStartup.cs @@ -11,6 +11,8 @@ using OrchardCore.Settings; using OrchardCore.Users.Controllers; using OrchardCore.Users.Drivers; +using OrchardCore.Users.Endpoints.EmailAuthenticator; +using OrchardCore.Users.Endpoints.SmsAuthenticator; using OrchardCore.Users.Events; using OrchardCore.Users.Filters; using OrchardCore.Users.Models; @@ -43,17 +45,25 @@ public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder ro _userOptions ??= serviceProvider.GetRequiredService>().Value; routes.MapAreaControllerRoute( - name: "LoginWithTwoFactorAuthentication", - areaName: UserConstants.Features.Users, - pattern: "LoginWithTwoFactorAuthentication", - defaults: new { controller = _twoFactorControllerName, action = nameof(TwoFactorAuthenticationController.LoginWithTwoFactorAuthentication) } - ); + name: "LoginWithTwoFactorAuthentication", + areaName: UserConstants.Features.Users, + pattern: "LoginWithTwoFactorAuthentication", + defaults: new + { + controller = _twoFactorControllerName, + action = nameof(TwoFactorAuthenticationController.LoginWithTwoFactorAuthentication), + } + ); routes.MapAreaControllerRoute( name: "TwoFactorAuthentication", areaName: UserConstants.Features.Users, pattern: _userOptions.TwoFactorAuthenticationPath, - defaults: new { controller = _twoFactorControllerName, action = nameof(TwoFactorAuthenticationController.Index) } + defaults: new + { + controller = _twoFactorControllerName, + action = nameof(TwoFactorAuthenticationController.Index), + } ); } } @@ -105,6 +115,11 @@ public override void ConfigureServices(IServiceCollection services) services.AddScoped, TwoFactorMethodLoginEmailDisplayDriver>(); services.AddScoped, EmailAuthenticatorLoginSettingsDisplayDriver>(); } + + public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) + { + routes.AddEmailSendCodeEndpoint(); + } } [Feature(UserConstants.Features.SmsAuthenticator)] @@ -124,4 +139,9 @@ public override void ConfigureServices(IServiceCollection services) services.AddScoped, TwoFactorMethodLoginSmsDisplayDriver>(); services.AddScoped, SmsAuthenticatorLoginSettingsDisplayDriver>(); } + + public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) + { + routes.AddSmsSendCodeEndpoint(); + } } diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Views/EmailAuthenticatorValidation.cshtml b/src/OrchardCore.Modules/OrchardCore.Users/Views/EmailAuthenticatorValidation.cshtml index 7b6349bd737..bdec0394aab 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Views/EmailAuthenticatorValidation.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Users/Views/EmailAuthenticatorValidation.cshtml @@ -1,18 +1,11 @@ @using Microsoft.AspNetCore.Antiforgery -@inject IAntiforgery AntiforgeryService -@{ - var tokenSet = AntiforgeryService.GetAndStoreTokens(ViewContext.HttpContext); -} +@using OrchardCore.Users.Endpoints.EmailAuthenticator +
@T["Please click here to receive a one-time verification code via email."]
-
-
+
-
+