Skip to content

Commit 50b553d

Browse files
Merge OPC main OPCFoundation#200
2 parents c502655 + ad23bb1 commit 50b553d

File tree

8 files changed

+163
-20
lines changed

8 files changed

+163
-20
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# UA Cloud Library
22

3-
The reference implementation of the UA Cloud Library. The UA Cloud Library enables the storage in and querying of OPC UA Information Models from anywhere in the world.
3+
The reference implementation of the UA Cloud Library. The UA Cloud Library enables the storage in and querying of OPC UA Information Models from anywhere in the world.
44

55
## Features
66

UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,55 @@
22
@model ForgotPasswordModel
33
@{
44
ViewData["Title"] = "Forgot your password?";
5+
var _reCaptchaUrl = $"{@Model.CaptchaSettings.ClientApiUrl}{@Model.CaptchaSettings.SiteKey}";
56
}
67

78
<h1>@ViewData["Title"]</h1>
89
<h2>Enter your email.</h2>
910
<hr />
1011
<div class="row">
1112
<div class="col-md-4">
12-
<form method="post">
13+
<form id="resetForm" method="post">
1314
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
15+
<span asp-validation-for="CaptchaResponseToken" class="text-danger"></span>
1416
<div class="form-floating">
1517
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" />
1618
<label asp-for="Input.Email" class="form-label"></label>
1719
<span asp-validation-for="Input.Email" class="text-danger"></span>
1820
</div>
19-
<button type="submit" class="w-100 btn btn-lg btn-primary">Reset Password</button>
21+
<input type="hidden" asp-for="CaptchaResponseToken" id="CaptchaResponseToken" />
22+
@if (!Model.CaptchaSettings.Enabled)
23+
{
24+
<button id="resetSubmit" type="submit" class="w-100 btn btn-lg btn-primary">Reset Password</button>
25+
}
26+
else
27+
{
28+
<button id="resetSubmit" type="button" class="w-100 btn btn-lg btn-primary">Reset Password</button>
29+
}
2030
</form>
2131
</div>
2232
</div>
2333

2434
@section Scripts {
2535
<partial name="_ValidationScriptsPartial" />
36+
@if (Model.CaptchaSettings.Enabled)
37+
{
38+
<script src='@_reCaptchaUrl'></script>
39+
<script>
40+
41+
function reCaptchaExecute() {
42+
grecaptcha.execute('@Model.CaptchaSettings.SiteKey', { action: 'register' }).then(function (token) {
43+
//populate token value in hidden field
44+
document.getElementById("CaptchaResponseToken").value = token;
45+
//submit form
46+
$('#resetForm').submit();
47+
});
48+
}
49+
50+
//wire up reset button to execute reCaptcha before submitting
51+
const btnResetSubmit = document.getElementById("resetSubmit");
52+
btnResetSubmit.addEventListener('click', reCaptchaExecute, false);
53+
//Note - if run on load, the token expires after 2 mins
54+
</script>
55+
}
2656
}

UACloudLibraryServer/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,43 @@
1313
using Microsoft.AspNetCore.Mvc;
1414
using Microsoft.AspNetCore.Mvc.RazorPages;
1515
using Microsoft.AspNetCore.WebUtilities;
16+
using Microsoft.Extensions.Configuration;
1617
using Opc.Ua.Cloud.Library.Authentication;
18+
using Opc.Ua.Cloud.Library.Interfaces;
1719

1820
namespace Opc.Ua.Cloud.Library.Areas.Identity.Pages.Account
1921
{
2022
public class ForgotPasswordModel : PageModel
2123
{
2224
private readonly UserManager<IdentityUser> _userManager;
2325
private readonly IEmailSender _emailSender;
26+
private readonly Interfaces.ICaptchaValidation _captchaValidation;
27+
private readonly CaptchaSettings _captchaSettings;
2428

25-
public ForgotPasswordModel(UserManager<IdentityUser> userManager, IEmailSender emailSender)
29+
public ForgotPasswordModel(
30+
UserManager<IdentityUser> userManager,
31+
IEmailSender emailSender,
32+
IConfiguration configuration,
33+
Interfaces.ICaptchaValidation captchaValidation)
2634
{
2735
_userManager = userManager;
2836
_emailSender = emailSender;
37+
_captchaValidation = captchaValidation;
38+
39+
_captchaSettings = new CaptchaSettings();
40+
configuration.GetSection("CaptchaSettings").Bind(_captchaSettings);
2941
}
3042

43+
/// Populate values for cshtml to use
44+
/// </summary>
45+
public CaptchaSettings CaptchaSettings { get { return _captchaSettings; } }
46+
47+
/// <summary>
48+
/// Populate a token returned from client side call to Google Captcha
49+
/// </summary>
50+
[BindProperty]
51+
public string CaptchaResponseToken { get; set; }
52+
3153
/// <summary>
3254
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
3355
/// directly from your code. This API may change or be removed in future releases.
@@ -52,6 +74,10 @@ public class InputModel
5274

5375
public async Task<IActionResult> OnPostAsync()
5476
{
77+
//Captcha validate
78+
var captchaResult = await _captchaValidation.ValidateCaptcha(CaptchaResponseToken);
79+
if (!string.IsNullOrEmpty(captchaResult)) ModelState.AddModelError("CaptchaResponseToken", captchaResult);
80+
5581
if (ModelState.IsValid)
5682
{
5783
var user = await _userManager.FindByEmailAsync(Input.Email).ConfigureAwait(false);

UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
@{
44
ViewData["Title"] = "Manage Email";
55
ViewData["ActivePage"] = ManageNavPages.Email;
6+
var _reCaptchaUrl = $"{@Model.CaptchaSettings.ClientApiUrl}{@Model.CaptchaSettings.SiteKey}";
67
}
78

89
<h3>@ViewData["Title"]</h3>
910
<partial name="_StatusMessage" for="StatusMessage" />
1011
<div class="row">
1112
<div class="col-md-6">
12-
<form id="email-form" method="post">
13+
<form id="emailForm" method="post">
1314
<div asp-validation-summary="All" class="text-danger"></div>
15+
<span asp-validation-for="CaptchaResponseToken" class="text-danger"></span>
16+
<input type="hidden" asp-for="CaptchaResponseToken" id="CaptchaResponseToken" />
1417
@if (Model.IsEmailConfirmed)
1518
{
1619
<div class="form-floating input-group">
@@ -26,19 +29,53 @@
2629
<div class="form-floating">
2730
<input asp-for="Email" class="form-control" disabled />
2831
<label asp-for="Email" class="form-label"></label>
29-
<button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
32+
<button id="emailVerificationSubmit" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
3033
</div>
3134
}
3235
<div class="form-floating">
3336
<input asp-for="Input.NewEmail" class="form-control" autocomplete="email" aria-required="true" />
3437
<label asp-for="Input.NewEmail" class="form-label"></label>
3538
<span asp-validation-for="Input.NewEmail" class="text-danger"></span>
3639
</div>
37-
<button id="change-email-button" type="submit" asp-page-handler="ChangeEmail" class="w-100 btn btn-lg btn-primary">Change email</button>
40+
<button id="changeEmailSubmit" type="submit" asp-page-handler="ChangeEmail" class="w-100 btn btn-lg btn-primary">Change email</button>
3841
</form>
3942
</div>
4043
</div>
4144

4245
@section Scripts {
4346
<partial name="_ValidationScriptsPartial" />
47+
@if (Model.CaptchaSettings.Enabled)
48+
{
49+
<script src='@_reCaptchaUrl'></script>
50+
<script>
51+
function reCaptchaExecuteVerify() {
52+
grecaptcha.execute('@Model.CaptchaSettings.SiteKey', { action: 'reverifyEmail' }).then(function (token) {
53+
//populate token value in hidden field
54+
document.getElementById("CaptchaResponseToken").value = token;
55+
//submit form
56+
$('#emailForm').submit();
57+
});
58+
}
59+
60+
//wire up register button to execute reCaptcha before submitting
61+
const btnVerifySubmit = document.getElementById("emailVerificationSubmit");
62+
btnVerifySubmit.addEventListener('click', reCaptchaExecuteVerify, false);
63+
//Note - if run on load, the token expires after 2 mins
64+
65+
function reCaptchaExecute() {
66+
grecaptcha.execute('@Model.CaptchaSettings.SiteKey', { action: 'reverifyEmail' }).then(function (token) {
67+
//populate token value in hidden field
68+
document.getElementById("CaptchaResponseToken").value = token;
69+
//submit form
70+
$('#emailForm').submit();
71+
});
72+
}
73+
74+
//wire up register button to execute reCaptcha before submitting
75+
const btnChangeEmail = document.getElementById("changeEmailSubmit");
76+
btnChangeEmail.addEventListener('click', reCaptchaExecute, false);
77+
//Note - if run on load, the token expires after 2 mins
78+
</script>
79+
}
80+
4481
}

UACloudLibraryServer/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.AspNetCore.Mvc;
1212
using Microsoft.AspNetCore.Mvc.RazorPages;
1313
using Microsoft.AspNetCore.WebUtilities;
14+
using Microsoft.Extensions.Configuration;
1415
using Opc.Ua.Cloud.Library.Authentication;
1516

1617
namespace Opc.Ua.Cloud.Library.Areas.Identity.Pages.Account.Manage
@@ -20,15 +21,23 @@ public class EmailModel : PageModel
2021
private readonly UserManager<IdentityUser> _userManager;
2122
private readonly SignInManager<IdentityUser> _signInManager;
2223
private readonly IEmailSender _emailSender;
24+
private readonly Interfaces.ICaptchaValidation _captchaValidation;
25+
private readonly CaptchaSettings _captchaSettings;
2326

2427
public EmailModel(
2528
UserManager<IdentityUser> userManager,
2629
SignInManager<IdentityUser> signInManager,
27-
IEmailSender emailSender)
30+
IEmailSender emailSender,
31+
IConfiguration configuration,
32+
Interfaces.ICaptchaValidation captchaValidation)
2833
{
2934
_userManager = userManager;
3035
_signInManager = signInManager;
3136
_emailSender = emailSender;
37+
_captchaValidation = captchaValidation;
38+
39+
_captchaSettings = new CaptchaSettings();
40+
configuration.GetSection("CaptchaSettings").Bind(_captchaSettings);
3241
}
3342

3443
/// <summary>
@@ -43,6 +52,17 @@ public EmailModel(
4352
/// </summary>
4453
public bool IsEmailConfirmed { get; set; }
4554

55+
/// <summary>
56+
/// Populate values for cshtml to use
57+
/// </summary>
58+
public CaptchaSettings CaptchaSettings { get { return _captchaSettings; } }
59+
60+
/// <summary>
61+
/// Populate a token returned from client side call to Google Captcha
62+
/// </summary>
63+
[BindProperty]
64+
public string CaptchaResponseToken { get; set; }
65+
4666
/// <summary>
4767
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
4868
/// directly from your code. This API may change or be removed in future releases.
@@ -105,6 +125,9 @@ public async Task<IActionResult> OnPostChangeEmailAsync()
105125
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
106126
}
107127

128+
var captchaResult = await _captchaValidation.ValidateCaptcha(CaptchaResponseToken);
129+
if (!string.IsNullOrEmpty(captchaResult)) ModelState.AddModelError("CaptchaResponseToken", captchaResult);
130+
108131
if (!ModelState.IsValid)
109132
{
110133
await LoadAsync(user);
@@ -145,6 +168,10 @@ public async Task<IActionResult> OnPostSendVerificationEmailAsync()
145168
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
146169
}
147170

171+
//Captcha validate
172+
var captchaResult = await _captchaValidation.ValidateCaptcha(CaptchaResponseToken);
173+
if (!string.IsNullOrEmpty(captchaResult)) ModelState.AddModelError("CaptchaResponseToken", captchaResult);
174+
148175
if (!ModelState.IsValid)
149176
{
150177
await LoadAsync(user).ConfigureAwait(false);

UACloudLibraryServer/Areas/Identity/Pages/Account/Register.cshtml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,8 @@
6262
{
6363
<div>
6464
<p>
65-
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">
66-
article
67-
about setting up this ASP.NET application to support logging in via external services
68-
</a>.
65+
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
66+
about setting up this ASP.NET application to support logging in via external services</a>.
6967
</p>
7068
</div>
7169
}

UACloudLibraryServer/CaptchaValidation.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,6 @@ public async Task<string> ValidateCaptcha(string responseToken)
162162
return "Error(s) occurred validating the Captcha response. Please contact your system administrator.";
163163
}
164164

165-
// check reCaptcha response action
166-
//if (recaptchaResponse.action.ToUpper() != expected_action.ToUpper())
167-
//{
168-
// //Logging.Log(new Logging.LogItem { Msg = $"Google RecCaptcha action doesn't match:\nExpected action: {expected_action} Given action: {recaptcha_response.action}" }, DefaultLogValues);
169-
// return (recaptchaResponse, false);
170-
//}
171-
172165
// anything less than 0.5 is a bot
173166
if (recaptchaResponse.score < _captchaSettings.BotThreshold)
174167
{
@@ -191,7 +184,6 @@ public async Task<string> ValidateCaptcha(string responseToken)
191184
}
192185
finally
193186
{
194-
// Dispose once all HttpClient calls are complete. This is not necessary if the containing object will be disposed of; for example in this case the HttpClient instance will be disposed automatically when the application terminates so the following call is superfluous.
195187
client.Dispose();
196188
}
197189
}

azure-pipelines.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# .NET Desktop
2+
# Build and run tests for .NET Desktop or Windows classic desktop solutions.
3+
# Add steps that publish symbols, save build artifacts, and more:
4+
# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net
5+
6+
trigger:
7+
- main
8+
9+
pool:
10+
vmImage: 'windows-latest'
11+
12+
variables:
13+
solution: '**/*.sln'
14+
buildPlatform: 'Any CPU'
15+
buildConfiguration: 'Release'
16+
17+
steps:
18+
- task: NuGetToolInstaller@1
19+
20+
- task: NuGetCommand@2
21+
inputs:
22+
restoreSolution: '$(solution)'
23+
24+
- task: VSBuild@1
25+
inputs:
26+
solution: '$(solution)'
27+
platform: '$(buildPlatform)'
28+
configuration: '$(buildConfiguration)'
29+
30+
- task: VSTest@2
31+
inputs:
32+
platform: '$(buildPlatform)'
33+
configuration: '$(buildConfiguration)'

0 commit comments

Comments
 (0)