diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs b/UACloudLibraryServer/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs index da52717a..0072b772 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; +using Opc.Ua.Cloud.Library.Authentication; namespace Opc.Ua.Cloud.Library.Areas.Identity.Pages.Account { @@ -171,8 +172,11 @@ public async Task OnPostConfirmationAsync(string returnUrl = 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 clicking here.").ConfigureAwait(false); + await EmailManager.SendConfirmExternalEmail( + _emailSender, + Input.Email, + callbackUrl + ).ConfigureAwait(false); // 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) diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs b/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs index d12f4d56..381d5873 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; +using Opc.Ua.Cloud.Library.Authentication; namespace Opc.Ua.Cloud.Library.Areas.Identity.Pages.Account { @@ -71,16 +72,11 @@ public async Task OnPostAsync() protocol: Request.Scheme); //notify user of password reset w/ reset link - StringBuilder sbBody = new StringBuilder(); - sbBody.AppendLine("

Reset Password

"); - sbBody.AppendLine("

A request has been made to reset your password in the CESMII Cloud Library."); - sbBody.AppendLine($"Please click here to reset your password.

"); - sbBody.AppendLine("

If you did not make this request, please contact the CESMII DevOps Team.

"); - sbBody.AppendLine("

The CESMII UA Cloud Library is hosted by CESMII, the Clean Energy Smart Manufacturing Institute! This Cloud Library contains curated node sets created by CESMII or its members, as well as node sets from the OPC Foundation Cloud Library.

"); - sbBody.AppendLine("

Sincerely,
CESMII DevOps Team

"); - - await _emailSender.SendEmailAsync(Input.Email, "CESMII | Cloud Library | Reset Password", - sbBody.ToString()).ConfigureAwait(false); + await EmailManager.SendPasswordReset( + _emailSender, + Input.Email, + callbackUrl + ).ConfigureAwait(false); return RedirectToPage("./ForgotPasswordConfirmation"); } diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/Login.cshtml b/UACloudLibraryServer/Areas/Identity/Pages/Account/Login.cshtml index 98a71bd9..c0ab95d6 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/Login.cshtml +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/Login.cshtml @@ -40,9 +40,6 @@

Register as a new user

-

- Resend email confirmation -

diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs index 63481e2a..db6c9ef2 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; +using Opc.Ua.Cloud.Library.Authentication; namespace Opc.Ua.Cloud.Library.Areas.Identity.Pages.Account.Manage { @@ -121,10 +122,12 @@ public async Task OnPostChangeEmailAsync() pageHandler: null, values: new { area = "Identity", userId = userId, email = Input.NewEmail, code = code }, protocol: Request.Scheme); - await _emailSender.SendEmailAsync( + + await EmailManager.SendConfirmEmailChange( + _emailSender, Input.NewEmail, - "Confirm your email", - $"Please confirm your account by clicking here.").ConfigureAwait(false); + callbackUrl + ).ConfigureAwait(false); StatusMessage = "Confirmation link to change email sent. Please check your email."; return RedirectToPage(); @@ -157,10 +160,12 @@ public async Task OnPostSendVerificationEmailAsync() pageHandler: null, values: new { area = "Identity", userId = userId, code = code }, protocol: Request.Scheme); - await _emailSender.SendEmailAsync( - email, - "Confirm your email", - $"Please confirm your account by clicking here.").ConfigureAwait(false); + + await EmailManager.SendReconfirmEmail( + _emailSender, + Input.NewEmail, + callbackUrl + ).ConfigureAwait(false); StatusMessage = "Verification email sent. Please check your email."; return RedirectToPage(); diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml index 135e25d7..4411f225 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/ManageApiKeys.cshtml @@ -43,7 +43,7 @@
- @Model.GeneratedApiKey + @Model.GeneratedApiKey @@ -63,7 +63,7 @@
- Usage: Add an HTTP Header X-Api-key with the API key as the header value. + Usage: Add an HTTP Header X-Api-key with the API key as the header value. Note that the key name does not need to be provided separately for authentication.
diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml.cs b/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml.cs index 637c6d79..254c1c94 100644 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Globalization; using System.Linq; using System.Text; using System.Text.Encodings.Web; @@ -18,6 +19,7 @@ using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Opc.Ua.Cloud.Library.Authentication; namespace Opc.Ua.Cloud.Library.Areas.Identity.Pages.Account { @@ -166,26 +168,12 @@ public async Task OnPostAsync(string returnUrl = null) protocol: Request.Scheme); //notify registering user - StringBuilder sbBody = new StringBuilder(); - sbBody.AppendLine("

Welcome to the CESMII UA Cloud Library

"); - sbBody.AppendLine("

Thank you for creating an account on the CESMII UA Cloud Library. "); - if (_userManager.Options.SignIn.RequireConfirmedAccount) - { - sbBody.AppendLine($"Please confirm your account by clicking here.

"); - } - sbBody.AppendLine("

The CESMII UA Cloud Library is hosted by CESMII, the Clean Energy Smart Manufacturing Institute! This Cloud Library contains curated node sets created by CESMII or its members, as well as node sets from the OPC Foundation Cloud Library.

"); - sbBody.AppendLine("

Sincerely,
CESMII DevOps Team

"); - - await _emailSender.SendEmailAsync(Input.Email, "CESMII | Cloud Library | New Account Confirmation", - sbBody.ToString()); - - //notify CESMII dev ops as well - StringBuilder sbBody2 = new StringBuilder(); - sbBody2.AppendLine("

CESMII UA Cloud Library - New Account Sign Up

"); - sbBody2.AppendLine($"

User '{Input.Email}' created an account on the CESMII UA Cloud Library. "); - sbBody2.AppendLine("

The CESMII UA Cloud Library is hosted by CESMII, the Clean Energy Smart Manufacturing Institute! This Cloud Library contains curated node sets created by CESMII or its members, as well as node sets from the OPC Foundation Cloud Library.

"); - sbBody2.AppendLine("

Sincerely,
CESMII DevOps Team

"); - await _emailSender.SendEmailAsync("devops@cesmii.org", "CESMII | Cloud Library | New Account Sign Up", sbBody2.ToString()).ConfigureAwait(false); + await EmailManager.SendConfirmRegistration( + _emailSender, + Input.Email, + callbackUrl, + _userManager.Options.SignIn.RequireConfirmedAccount + ).ConfigureAwait(false); if (_userManager.Options.SignIn.RequireConfirmedAccount) { diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml b/UACloudLibraryServer/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml deleted file mode 100644 index cd983d7e..00000000 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml +++ /dev/null @@ -1,26 +0,0 @@ -@page -@model ResendEmailConfirmationModel -@{ - ViewData["Title"] = "Resend email confirmation"; -} - -

@ViewData["Title"]

-

Enter your email.

-
-
-
-
-
-
- - - -
- -
-
-
- -@section Scripts { - -} diff --git a/UACloudLibraryServer/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs b/UACloudLibraryServer/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs deleted file mode 100644 index 3f17f501..00000000 --- a/UACloudLibraryServer/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -#nullable disable - -using System.ComponentModel.DataAnnotations; -using System.Text; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.UI.Services; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.AspNetCore.WebUtilities; - -namespace Opc.Ua.Cloud.Library.Areas.Identity.Pages.Account -{ - [AllowAnonymous] - public class ResendEmailConfirmationModel : PageModel - { - private readonly UserManager _userManager; - private readonly IEmailSender _emailSender; - - public ResendEmailConfirmationModel(UserManager userManager, IEmailSender emailSender) - { - _userManager = userManager; - _emailSender = emailSender; - } - - /// - /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - [BindProperty] - public InputModel Input { get; set; } - - /// - /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public class InputModel - { - /// - /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - [Required] - [EmailAddress] - public string Email { get; set; } - } - - public void OnGet() - { - } - - public async Task OnPostAsync() - { - if (!ModelState.IsValid) - { - return Page(); - } - - var user = await _userManager.FindByEmailAsync(Input.Email).ConfigureAwait(false); - if (user == null) - { - ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); - return Page(); - } - - var userId = await _userManager.GetUserIdAsync(user).ConfigureAwait(false); - var code = await _userManager.GenerateEmailConfirmationTokenAsync(user).ConfigureAwait(false); - code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - var callbackUrl = Url.Page( - "/Account/ConfirmEmail", - pageHandler: null, - values: new { userId = userId, code = code }, - protocol: Request.Scheme); - await _emailSender.SendEmailAsync( - Input.Email, - "Confirm your email", - $"Please confirm your account by clicking here.").ConfigureAwait(false); - - ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); - return Page(); - } - } -} diff --git a/UACloudLibraryServer/Authentication/BasicAuthenticationHandler.cs b/UACloudLibraryServer/Authentication/BasicAuthenticationHandler.cs index 2de176c2..c8e5c45e 100644 --- a/UACloudLibraryServer/Authentication/BasicAuthenticationHandler.cs +++ b/UACloudLibraryServer/Authentication/BasicAuthenticationHandler.cs @@ -30,7 +30,6 @@ namespace Opc.Ua.Cloud.Library.Authentication { using System; - using System.Collections.Generic; using System.Linq; using System.Net.Http.Headers; using System.Security.Claims; @@ -38,10 +37,8 @@ namespace Opc.Ua.Cloud.Library.Authentication using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; - using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; - using Microsoft.Extensions.Primitives; using Opc.Ua.Cloud.Library.Interfaces; public class BasicAuthenticationHandler : AuthenticationHandler diff --git a/UACloudLibraryServer/Authentication/SignedInUserAuthenticationHandler.cs b/UACloudLibraryServer/Authentication/SignedInUserAuthenticationHandler.cs index c36a2d18..bf008c23 100644 --- a/UACloudLibraryServer/Authentication/SignedInUserAuthenticationHandler.cs +++ b/UACloudLibraryServer/Authentication/SignedInUserAuthenticationHandler.cs @@ -30,19 +30,13 @@ namespace Opc.Ua.Cloud.Library.Authentication { using System; - using System.Collections.Generic; - using System.Linq; - using System.Net.Http.Headers; using System.Security.Claims; - using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; - using Microsoft.Extensions.Primitives; - using Opc.Ua.Cloud.Library.Interfaces; public class SignedInUserAuthenticationHandler : AuthenticationHandler { diff --git a/UACloudLibraryServer/EmailManager.cs b/UACloudLibraryServer/EmailManager.cs new file mode 100644 index 00000000..57405bd0 --- /dev/null +++ b/UACloudLibraryServer/EmailManager.cs @@ -0,0 +1,148 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System; +using System.Globalization; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; + +namespace Opc.Ua.Cloud.Library +{ + public class EmailManager + { + const string EmailTemplate = @" + +

{0} Please click here to continue.

+

If the above link does not work, please copy and paste the link below into your browser’s address bar and press enter:

+

{2}

+

If you experience difficulty with this site, please reply to this email for help.

+

+

The CESMII UA Cloud Library is hosted by CESMII, the Clean Energy Smart Manufacturing Institute! + This Cloud Library contains curated node sets created by CESMII or its members, as well as node sets from the OPC Foundation Cloud Library. +

+ "; + + //const string EmailTemplate = @" + // + //

+ //

The Industrial Interoperability Standard ™

+ //

 

+ //

{0} Please click here to continue.

+ //

If the above link does not work, please copy and paste the link below into your browser’s address bar and press enter:

+ //

{2}

+ //

If you experience difficulty with this site, please reply to this email for help.

+ //

+ //

OPC Foundation
+ // 16101 North 82nd Street, Suite 3B
+ // Scottsdale, Arizona 85260-1868 US
+ // +1 (480) 483-6644
+ //

Click here to unsubscribe.

+ // "; + + private static async Task Send(IEmailSender emailSender, string email, string subject, string action, string url) + { + var body = string.Format( + CultureInfo.InvariantCulture, + EmailTemplate, + action, + HtmlEncoder.Default.Encode(url), + url); + + await emailSender.SendEmailAsync( + email, + subject, + body).ConfigureAwait(false); + } + + internal static async Task SendConfirmRegistration(IEmailSender emailSender, string email, string url, bool requireConfirmedAccount) + { + StringBuilder sbBody = new StringBuilder(); + sbBody.AppendLine("

Welcome to the CESMII UA Cloud Library

"); + sbBody.AppendLine("

Thank you for creating an account on the CESMII UA Cloud Library. "); + if (requireConfirmedAccount) + { + sbBody.AppendLine($"Please confirm your account by clicking here.

".ToString()); + } + sbBody.AppendLine("

The CESMII UA Cloud Library is hosted by CESMII, the Clean Energy Smart Manufacturing Institute! This Cloud Library contains curated node sets created by CESMII or its members, as well as node sets from the OPC Foundation Cloud Library.

"); + sbBody.AppendLine("

Sincerely,
CESMII DevOps Team

"); + await emailSender.SendEmailAsync( + email, + "CESMII | Cloud Library | New Account Confirmation", + sbBody.ToString()).ConfigureAwait(false); + + //notify CESMII dev ops as well + StringBuilder sbBody2 = new StringBuilder(); + sbBody2.AppendLine("

CESMII UA Cloud Library - New Account Sign Up

"); + sbBody2.AppendLine($"

User '{email}' created an account on the CESMII UA Cloud Library.".ToString()); + sbBody2.AppendLine("

The CESMII UA Cloud Library is hosted by CESMII, the Clean Energy Smart Manufacturing Institute! This Cloud Library contains curated node sets created by CESMII or its members, as well as node sets from the OPC Foundation Cloud Library.

"); + sbBody2.AppendLine("

Sincerely,
CESMII DevOps Team

"); + await emailSender.SendEmailAsync( + "devops@cesmii.org", + "CESMII | Cloud Library | New Account Sign Up", + sbBody2.ToString()).ConfigureAwait(false); + //return Send(emailSender, email, "UA Cloud Library - Confirm Your Email", "Please confirm your email to complete registration.", url); + } + + internal static Task SendConfirmExternalEmail(IEmailSender emailSender, string email, string url) + { + return Send(emailSender, email, "CESMII | Cloud Library | Confirm Your Email", "Please confirm your email to complete registration.", url); + //return Send(emailSender, email, "UA Cloud Library - Confirm Your Email", "Please confirm your email to complete registration.", url); + } + + internal static Task SendConfirmEmailChange(IEmailSender emailSender, string newEmail, string url) + { + return Send(emailSender, newEmail, "CESMII | Cloud Library | Confirm New Email", "Please confirm your email to complete registration.", url); + } + + internal static Task SendPasswordReset(IEmailSender emailSender, string email, string url) + { + StringBuilder sbBody = new StringBuilder(); + sbBody.AppendLine("

Reset Password

"); + sbBody.AppendLine("

A request has been made to reset your password in the CESMII Cloud Library."); + sbBody.AppendLine($"Please click here to reset your password.

".ToString()); + sbBody.AppendLine("

If you did not make this request, please contact the CESMII DevOps Team.

"); + sbBody.AppendLine("

The CESMII UA Cloud Library is hosted by CESMII, the Clean Energy Smart Manufacturing Institute! This Cloud Library contains curated node sets created by CESMII or its members, as well as node sets from the OPC Foundation Cloud Library.

"); + sbBody.AppendLine("

Sincerely,
CESMII DevOps Team

"); + return emailSender.SendEmailAsync( + email, + "CESMII | Cloud Library | Reset Password", + sbBody.ToString()); + //return Send(emailSender, email, "UA Cloud Library - Reset Password", "We received a request to reset your password.", url); + } + + internal static Task SendReconfirmEmail(IEmailSender emailSender, string newEmail, string url) + { + return Send(emailSender, newEmail, "CESMII | Cloud Library | Verify Your Email", "Please verify your email address.", url); + //return Send(emailSender, newEmail, "UA Cloud Library - Verify Your Email","Please verify your email address.", url); + } + } +} diff --git a/UACloudLibraryServer/SendGridEmailSender.cs b/UACloudLibraryServer/SendGridEmailSender.cs index 0527d27a..8e65daff 100644 --- a/UACloudLibraryServer/SendGridEmailSender.cs +++ b/UACloudLibraryServer/SendGridEmailSender.cs @@ -53,9 +53,9 @@ public Task SendEmailAsync(string email, string subject, string htmlMessage) From = new EmailAddress(emailFrom, "CESMII Dev Ops"), ReplyTo = new EmailAddress(emailReplyTo), Subject = subject, - PlainTextContent = htmlMessage, HtmlContent = htmlMessage }; + msg.AddTo(new EmailAddress(email)); // Disable click tracking.