8 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using UserManagementReact.Entities;
8 | using Microsoft.AspNetCore.Identity;
9 | using Microsoft.AspNetCore.Mvc;
10 | using Microsoft.AspNetCore.Mvc.RazorPages;
11 | using Microsoft.AspNetCore.WebUtilities;
12 |
13 | namespace UserManagementReact.Areas.Identity.Pages.Account
14 | {
15 | [AllowAnonymous]
16 | public class ConfirmEmailModel : PageModel
17 | {
18 | private readonly UserManager _userManager;
19 |
20 | public ConfirmEmailModel(UserManager userManager)
21 | {
22 | _userManager = userManager;
23 | }
24 |
25 | [TempData]
26 | public string StatusMessage { get; set; }
27 |
28 | public async Task OnGetAsync(string userId, string code)
29 | {
30 | if (userId == null || code == null)
31 | {
32 | return RedirectToPage("/Index");
33 | }
34 |
35 | var user = await _userManager.FindByIdAsync(userId);
36 | if (user == null)
37 | {
38 | return NotFound($"Unable to load user with ID '{userId}'.");
39 | }
40 |
41 | code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
42 | var result = await _userManager.ConfirmEmailAsync(user, code);
43 | StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
44 | return Page();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ConfirmEmailChangeModel
3 | @{
4 | ViewData["Title"] = "Confirm email change";
5 | }
6 |
7 |
@ViewData["Title"]
8 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using UserManagementReact.Entities;
8 | using Microsoft.AspNetCore.Identity;
9 | using Microsoft.AspNetCore.Mvc;
10 | using Microsoft.AspNetCore.Mvc.RazorPages;
11 | using Microsoft.AspNetCore.WebUtilities;
12 |
13 | namespace UserManagementReact.Areas.Identity.Pages.Account
14 | {
15 | [AllowAnonymous]
16 | public class ConfirmEmailChangeModel : PageModel
17 | {
18 | private readonly UserManager _userManager;
19 | private readonly SignInManager _signInManager;
20 |
21 | public ConfirmEmailChangeModel(UserManager userManager, SignInManager signInManager)
22 | {
23 | _userManager = userManager;
24 | _signInManager = signInManager;
25 | }
26 |
27 | [TempData]
28 | public string StatusMessage { get; set; }
29 |
30 | public async Task OnGetAsync(string userId, string email, string code)
31 | {
32 | if (userId == null || email == null || code == null)
33 | {
34 | return RedirectToPage("/Index");
35 | }
36 |
37 | var user = await _userManager.FindByIdAsync(userId);
38 | if (user == null)
39 | {
40 | return NotFound($"Unable to load user with ID '{userId}'.");
41 | }
42 |
43 | code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
44 | var result = await _userManager.ChangeEmailAsync(user, email, code);
45 | if (!result.Succeeded)
46 | {
47 | StatusMessage = "Error changing email.";
48 | return Page();
49 | }
50 |
51 | // In our UI email and user name are one and the same, so when we update the email
52 | // we need to update the user name.
53 | var setUserNameResult = await _userManager.SetUserNameAsync(user, email);
54 | if (!setUserNameResult.Succeeded)
55 | {
56 | StatusMessage = "Error changing user name.";
57 | return Page();
58 | }
59 |
60 | await _signInManager.RefreshSignInAsync(user);
61 | StatusMessage = "Thank you for confirming your email change.";
62 | return Page();
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/ExternalLogin.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ExternalLoginModel
3 | @{
4 | ViewData["Title"] = "Register";
5 | }
6 |
7 |
@ViewData["Title"]
8 |
Associate your @Model.LoginProvider account.
9 |
10 |
11 |
12 | You've successfully authenticated with @Model.LoginProvider.
13 | Please enter an email address for this site below and click the Register button to finish
14 | logging in.
15 |
67 | There are no external authentication services configured. See this article
68 | for details on setting up this ASP.NET application to support logging in via external services.
69 |
38 |
39 | @section Scripts {
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using UserManagementReact.Entities;
8 | using Microsoft.AspNetCore.Identity;
9 | using Microsoft.AspNetCore.Mvc;
10 | using Microsoft.AspNetCore.Mvc.RazorPages;
11 | using Microsoft.Extensions.Logging;
12 |
13 | namespace UserManagementReact.Areas.Identity.Pages.Account
14 | {
15 | [AllowAnonymous]
16 | public class LoginWith2faModel : PageModel
17 | {
18 | private readonly SignInManager _signInManager;
19 | private readonly ILogger _logger;
20 |
21 | public LoginWith2faModel(SignInManager signInManager, ILogger logger)
22 | {
23 | _signInManager = signInManager;
24 | _logger = logger;
25 | }
26 |
27 | [BindProperty]
28 | public InputModel Input { get; set; }
29 |
30 | public bool RememberMe { get; set; }
31 |
32 | public string ReturnUrl { get; set; }
33 |
34 | public class InputModel
35 | {
36 | [Required]
37 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
38 | [DataType(DataType.Text)]
39 | [Display(Name = "Authenticator code")]
40 | public string TwoFactorCode { get; set; }
41 |
42 | [Display(Name = "Remember this machine")]
43 | public bool RememberMachine { get; set; }
44 | }
45 |
46 | public async Task OnGetAsync(bool rememberMe, string returnUrl = null)
47 | {
48 | // Ensure the user has gone through the username & password screen first
49 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
50 |
51 | if (user == null)
52 | {
53 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
54 | }
55 |
56 | ReturnUrl = returnUrl;
57 | RememberMe = rememberMe;
58 |
59 | return Page();
60 | }
61 |
62 | public async Task OnPostAsync(bool rememberMe, string returnUrl = null)
63 | {
64 | if (!ModelState.IsValid)
65 | {
66 | return Page();
67 | }
68 |
69 | returnUrl = returnUrl ?? Url.Content("~/");
70 |
71 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
72 | if (user == null)
73 | {
74 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
75 | }
76 |
77 | var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
78 |
79 | var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
80 |
81 | if (result.Succeeded)
82 | {
83 | _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
84 | return LocalRedirect(returnUrl);
85 | }
86 | else if (result.IsLockedOut)
87 | {
88 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
89 | return RedirectToPage("./Lockout");
90 | }
91 | else
92 | {
93 | _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
94 | ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
95 | return Page();
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model LoginWithRecoveryCodeModel
3 | @{
4 | ViewData["Title"] = "Recovery code verification";
5 | }
6 |
7 |
@ViewData["Title"]
8 |
9 |
10 | You have requested to log in with a recovery code. This login will not be remembered until you provide
11 | an authenticator app code at log in or disable 2FA and log in again.
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 | @section Scripts {
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using UserManagementReact.Entities;
8 | using Microsoft.AspNetCore.Identity;
9 | using Microsoft.AspNetCore.Mvc;
10 | using Microsoft.AspNetCore.Mvc.RazorPages;
11 | using Microsoft.Extensions.Logging;
12 |
13 | namespace UserManagementReact.Areas.Identity.Pages.Account
14 | {
15 | [AllowAnonymous]
16 | public class LoginWithRecoveryCodeModel : PageModel
17 | {
18 | private readonly SignInManager _signInManager;
19 | private readonly ILogger _logger;
20 |
21 | public LoginWithRecoveryCodeModel(SignInManager signInManager, ILogger logger)
22 | {
23 | _signInManager = signInManager;
24 | _logger = logger;
25 | }
26 |
27 | [BindProperty]
28 | public InputModel Input { get; set; }
29 |
30 | public string ReturnUrl { get; set; }
31 |
32 | public class InputModel
33 | {
34 | [BindProperty]
35 | [Required]
36 | [DataType(DataType.Text)]
37 | [Display(Name = "Recovery Code")]
38 | public string RecoveryCode { get; set; }
39 | }
40 |
41 | public async Task OnGetAsync(string returnUrl = null)
42 | {
43 | // Ensure the user has gone through the username & password screen first
44 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
45 | if (user == null)
46 | {
47 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
48 | }
49 |
50 | ReturnUrl = returnUrl;
51 |
52 | return Page();
53 | }
54 |
55 | public async Task OnPostAsync(string returnUrl = null)
56 | {
57 | if (!ModelState.IsValid)
58 | {
59 | return Page();
60 | }
61 |
62 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
63 | if (user == null)
64 | {
65 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
66 | }
67 |
68 | var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
69 |
70 | var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
71 |
72 | if (result.Succeeded)
73 | {
74 | _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
75 | return LocalRedirect(returnUrl ?? Url.Content("~/"));
76 | }
77 | if (result.IsLockedOut)
78 | {
79 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
80 | return RedirectToPage("./Lockout");
81 | }
82 | else
83 | {
84 | _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
85 | ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
86 | return Page();
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Logout.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model LogoutModel
3 | @{
4 | ViewData["Title"] = "Log out";
5 | }
6 |
7 |
8 |
@ViewData["Title"]
9 |
You have successfully logged out of the application.
33 |
34 | @section Scripts {
35 |
36 | }
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using UserManagementReact.Entities;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 | using Microsoft.Extensions.Logging;
11 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
12 | {
13 | public class ChangePasswordModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly SignInManager _signInManager;
17 | private readonly ILogger _logger;
18 |
19 | public ChangePasswordModel(
20 | UserManager userManager,
21 | SignInManager signInManager,
22 | ILogger logger)
23 | {
24 | _userManager = userManager;
25 | _signInManager = signInManager;
26 | _logger = logger;
27 | }
28 |
29 | [BindProperty]
30 | public InputModel Input { get; set; }
31 |
32 | [TempData]
33 | public string StatusMessage { get; set; }
34 |
35 | public class InputModel
36 | {
37 | [Required]
38 | [DataType(DataType.Password)]
39 | [Display(Name = "Current password")]
40 | public string OldPassword { get; set; }
41 |
42 | [Required]
43 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
44 | [DataType(DataType.Password)]
45 | [Display(Name = "New password")]
46 | public string NewPassword { get; set; }
47 |
48 | [DataType(DataType.Password)]
49 | [Display(Name = "Confirm new password")]
50 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
51 | public string ConfirmPassword { get; set; }
52 | }
53 |
54 | public async Task OnGetAsync()
55 | {
56 | var user = await _userManager.GetUserAsync(User);
57 | if (user == null)
58 | {
59 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
60 | }
61 |
62 | var hasPassword = await _userManager.HasPasswordAsync(user);
63 | if (!hasPassword)
64 | {
65 | return RedirectToPage("./SetPassword");
66 | }
67 |
68 | return Page();
69 | }
70 |
71 | public async Task OnPostAsync()
72 | {
73 | if (!ModelState.IsValid)
74 | {
75 | return Page();
76 | }
77 |
78 | var user = await _userManager.GetUserAsync(User);
79 | if (user == null)
80 | {
81 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
82 | }
83 |
84 | var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
85 | if (!changePasswordResult.Succeeded)
86 | {
87 | foreach (var error in changePasswordResult.Errors)
88 | {
89 | ModelState.AddModelError(string.Empty, error.Description);
90 | }
91 | return Page();
92 | }
93 |
94 | await _signInManager.RefreshSignInAsync(user);
95 | _logger.LogInformation("User changed their password successfully.");
96 | StatusMessage = "Your password has been changed.";
97 |
98 | return RedirectToPage();
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model DeletePersonalDataModel
3 | @{
4 | ViewData["Title"] = "Delete Personal Data";
5 | ViewData["ActivePage"] = ManageNavPages.PersonalData;
6 | }
7 |
8 |
@ViewData["Title"]
9 |
10 |
11 |
12 | Deleting this data will permanently remove your account, and this cannot be recovered.
13 |
14 |
15 |
16 |
17 |
29 |
30 |
31 | @section Scripts {
32 |
33 | }
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 | using System.Threading.Tasks;
4 | using UserManagementReact.Entities;
5 | using Microsoft.AspNetCore.Identity;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.AspNetCore.Mvc.RazorPages;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
11 | {
12 | public class DeletePersonalDataModel : PageModel
13 | {
14 | private readonly UserManager _userManager;
15 | private readonly SignInManager _signInManager;
16 | private readonly ILogger _logger;
17 |
18 | public DeletePersonalDataModel(
19 | UserManager userManager,
20 | SignInManager signInManager,
21 | ILogger logger)
22 | {
23 | _userManager = userManager;
24 | _signInManager = signInManager;
25 | _logger = logger;
26 | }
27 |
28 | [BindProperty]
29 | public InputModel Input { get; set; }
30 |
31 | public class InputModel
32 | {
33 | [Required]
34 | [DataType(DataType.Password)]
35 | public string Password { get; set; }
36 | }
37 |
38 | public bool RequirePassword { get; set; }
39 |
40 | public async Task OnGet()
41 | {
42 | var user = await _userManager.GetUserAsync(User);
43 | if (user == null)
44 | {
45 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
46 | }
47 |
48 | RequirePassword = await _userManager.HasPasswordAsync(user);
49 | return Page();
50 | }
51 |
52 | public async Task OnPostAsync()
53 | {
54 | var user = await _userManager.GetUserAsync(User);
55 | if (user == null)
56 | {
57 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
58 | }
59 |
60 | RequirePassword = await _userManager.HasPasswordAsync(user);
61 | if (RequirePassword)
62 | {
63 | if (!await _userManager.CheckPasswordAsync(user, Input.Password))
64 | {
65 | ModelState.AddModelError(string.Empty, "Incorrect password.");
66 | return Page();
67 | }
68 | }
69 |
70 | var result = await _userManager.DeleteAsync(user);
71 | var userId = await _userManager.GetUserIdAsync(user);
72 | if (!result.Succeeded)
73 | {
74 | throw new InvalidOperationException($"Unexpected error occurred deleting user with ID '{userId}'.");
75 | }
76 |
77 | await _signInManager.SignOutAsync();
78 |
79 | _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
80 |
81 | return Redirect("~/");
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model Disable2faModel
3 | @{
4 | ViewData["Title"] = "Disable two-factor authentication (2FA)";
5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
6 | }
7 |
8 |
9 |
@ViewData["Title"]
10 |
11 |
12 |
13 | This action only disables 2FA.
14 |
15 |
16 | Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
17 | used in an authenticator app you should reset your authenticator keys.
18 |
19 |
20 |
21 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using UserManagementReact.Entities;
6 | using Microsoft.AspNetCore.Identity;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Mvc.RazorPages;
9 | using Microsoft.Extensions.Logging;
10 |
11 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
12 | {
13 | public class Disable2faModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly ILogger _logger;
17 |
18 | public Disable2faModel(
19 | UserManager userManager,
20 | ILogger logger)
21 | {
22 | _userManager = userManager;
23 | _logger = logger;
24 | }
25 |
26 | [TempData]
27 | public string StatusMessage { get; set; }
28 |
29 | public async Task OnGet()
30 | {
31 | var user = await _userManager.GetUserAsync(User);
32 | if (user == null)
33 | {
34 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
35 | }
36 |
37 | if (!await _userManager.GetTwoFactorEnabledAsync(user))
38 | {
39 | throw new InvalidOperationException($"Cannot disable 2FA for user with ID '{_userManager.GetUserId(User)}' as it's not currently enabled.");
40 | }
41 |
42 | return Page();
43 | }
44 |
45 | public async Task OnPostAsync()
46 | {
47 | var user = await _userManager.GetUserAsync(User);
48 | if (user == null)
49 | {
50 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
51 | }
52 |
53 | var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
54 | if (!disable2faResult.Succeeded)
55 | {
56 | throw new InvalidOperationException($"Unexpected error occurred disabling 2FA for user with ID '{_userManager.GetUserId(User)}'.");
57 | }
58 |
59 | _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User));
60 | StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app";
61 | return RedirectToPage("./TwoFactorAuthentication");
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model DownloadPersonalDataModel
3 | @{
4 | ViewData["Title"] = "Download Your Data";
5 | ViewData["ActivePage"] = ManageNavPages.PersonalData;
6 | }
7 |
8 |
@ViewData["Title"]
9 |
10 | @section Scripts {
11 |
12 | }
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using UserManagementReact.Entities;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 | using Microsoft.Extensions.Logging;
11 | using Newtonsoft.Json;
12 |
13 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
14 | {
15 | public class DownloadPersonalDataModel : PageModel
16 | {
17 | private readonly UserManager _userManager;
18 | private readonly ILogger _logger;
19 |
20 | public DownloadPersonalDataModel(
21 | UserManager userManager,
22 | ILogger logger)
23 | {
24 | _userManager = userManager;
25 | _logger = logger;
26 | }
27 |
28 | public async Task OnPostAsync()
29 | {
30 | var user = await _userManager.GetUserAsync(User);
31 | if (user == null)
32 | {
33 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
34 | }
35 |
36 | _logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User));
37 |
38 | // Only include personal data for download
39 | var personalData = new Dictionary();
40 | var personalDataProps = typeof(ApplicationUser).GetProperties().Where(
41 | prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
42 | foreach (var p in personalDataProps)
43 | {
44 | personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
45 | }
46 |
47 | Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json");
48 | return new FileContentResult(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(personalData)), "text/json");
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/Email.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model EmailModel
3 | @{
4 | ViewData["Title"] = "Manage Email";
5 | ViewData["ActivePage"] = ManageNavPages.Email;
6 | }
7 |
8 |
To use an authenticator app go through the following steps:
12 |
13 |
14 |
15 | Download a two-factor authenticator app like Microsoft Authenticator for
16 | Windows Phone,
17 | Android and
18 | iOS or
19 | Google Authenticator for
20 | Android and
21 | iOS.
22 |
23 |
24 |
25 |
Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.
32 | Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
33 | with a unique code. Enter the code in the confirmation box below.
34 |
16 | If you lose your device and don't have the recovery codes you will lose access to your account.
17 |
18 |
19 | Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
20 | used in an authenticator app you should reset your authenticator keys.
21 |
22 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using UserManagementReact.Entities;
6 | using Microsoft.AspNetCore.Identity;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Mvc.RazorPages;
9 | using Microsoft.Extensions.Logging;
10 |
11 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
12 | {
13 | public class GenerateRecoveryCodesModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly ILogger _logger;
17 |
18 | public GenerateRecoveryCodesModel(
19 | UserManager userManager,
20 | ILogger logger)
21 | {
22 | _userManager = userManager;
23 | _logger = logger;
24 | }
25 |
26 | [TempData]
27 | public string[] RecoveryCodes { get; set; }
28 |
29 | [TempData]
30 | public string StatusMessage { get; set; }
31 |
32 | public async Task OnGetAsync()
33 | {
34 | var user = await _userManager.GetUserAsync(User);
35 | if (user == null)
36 | {
37 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
38 | }
39 |
40 | var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
41 | if (!isTwoFactorEnabled)
42 | {
43 | var userId = await _userManager.GetUserIdAsync(user);
44 | throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' because they do not have 2FA enabled.");
45 | }
46 |
47 | return Page();
48 | }
49 |
50 | public async Task OnPostAsync()
51 | {
52 | var user = await _userManager.GetUserAsync(User);
53 | if (user == null)
54 | {
55 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
56 | }
57 |
58 | var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
59 | var userId = await _userManager.GetUserIdAsync(user);
60 | if (!isTwoFactorEnabled)
61 | {
62 | throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' as they do not have 2FA enabled.");
63 | }
64 |
65 | var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
66 | RecoveryCodes = recoveryCodes.ToArray();
67 |
68 | _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
69 | StatusMessage = "You have generated new recovery codes.";
70 | return RedirectToPage("./ShowRecoveryCodes");
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/Index.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model IndexModel
3 | @{
4 | ViewData["Title"] = "Profile";
5 | ViewData["ActivePage"] = ManageNavPages.Index;
6 | }
7 |
8 |
@ViewData["Title"]
9 |
10 |
11 |
12 |
25 |
26 |
27 |
28 | @section Scripts {
29 |
30 | }
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using UserManagementReact.Entities;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 |
11 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
12 | {
13 | public partial class IndexModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly SignInManager _signInManager;
17 |
18 | public IndexModel(
19 | UserManager userManager,
20 | SignInManager signInManager)
21 | {
22 | _userManager = userManager;
23 | _signInManager = signInManager;
24 | }
25 |
26 | public string Username { get; set; }
27 |
28 | [TempData]
29 | public string StatusMessage { get; set; }
30 |
31 | [BindProperty]
32 | public InputModel Input { get; set; }
33 |
34 | public class InputModel
35 | {
36 | [Phone]
37 | [Display(Name = "Phone number")]
38 | public string PhoneNumber { get; set; }
39 | }
40 |
41 | private async Task LoadAsync(ApplicationUser user)
42 | {
43 | var userName = await _userManager.GetUserNameAsync(user);
44 | var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
45 |
46 | Username = userName;
47 |
48 | Input = new InputModel
49 | {
50 | PhoneNumber = phoneNumber
51 | };
52 | }
53 |
54 | public async Task OnGetAsync()
55 | {
56 | var user = await _userManager.GetUserAsync(User);
57 | if (user == null)
58 | {
59 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
60 | }
61 |
62 | await LoadAsync(user);
63 | return Page();
64 | }
65 |
66 | public async Task OnPostAsync()
67 | {
68 | var user = await _userManager.GetUserAsync(User);
69 | if (user == null)
70 | {
71 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
72 | }
73 |
74 | if (!ModelState.IsValid)
75 | {
76 | await LoadAsync(user);
77 | return Page();
78 | }
79 |
80 | var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
81 | if (Input.PhoneNumber != phoneNumber)
82 | {
83 | var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
84 | if (!setPhoneResult.Succeeded)
85 | {
86 | var userId = await _userManager.GetUserIdAsync(user);
87 | throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{userId}'.");
88 | }
89 | }
90 |
91 | await _signInManager.RefreshSignInAsync(user);
92 | StatusMessage = "Your profile has been updated";
93 | return RedirectToPage();
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc.Rendering;
6 |
7 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
8 | {
9 | public static class ManageNavPages
10 | {
11 | public static string Index => "Index";
12 |
13 | public static string Email => "Email";
14 |
15 | public static string ChangePassword => "ChangePassword";
16 |
17 | public static string ExternalLogins => "ExternalLogins";
18 |
19 | public static string PersonalData => "PersonalData";
20 |
21 | public static string TwoFactorAuthentication => "TwoFactorAuthentication";
22 |
23 | public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
24 |
25 | public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
26 |
27 | public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
28 |
29 | public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
30 |
31 | public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
32 |
33 | public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
34 |
35 | private static string PageNavClass(ViewContext viewContext, string page)
36 | {
37 | var activePage = viewContext.ViewData["ActivePage"] as string
38 | ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
39 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model PersonalDataModel
3 | @{
4 | ViewData["Title"] = "Personal Data";
5 | ViewData["ActivePage"] = ManageNavPages.PersonalData;
6 | }
7 |
8 |
@ViewData["Title"]
9 |
10 |
11 |
12 |
Your account contains personal data that you have given us. This page allows you to download or delete that data.
13 |
14 | Deleting this data will permanently remove your account, and this cannot be recovered.
15 |
12 |
13 | If you reset your authenticator key your authenticator app will not work until you reconfigure it.
14 |
15 |
16 | This process disables 2FA until you verify your authenticator app.
17 | If you do not complete your authenticator app configuration you may lose access to your account.
18 |
19 |
20 |
21 |
24 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using UserManagementReact.Entities;
6 | using Microsoft.AspNetCore.Identity;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Mvc.RazorPages;
9 | using Microsoft.Extensions.Logging;
10 |
11 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
12 | {
13 | public class ResetAuthenticatorModel : PageModel
14 | {
15 | UserManager _userManager;
16 | private readonly SignInManager _signInManager;
17 | ILogger _logger;
18 |
19 | public ResetAuthenticatorModel(
20 | UserManager userManager,
21 | SignInManager signInManager,
22 | ILogger logger)
23 | {
24 | _userManager = userManager;
25 | _signInManager = signInManager;
26 | _logger = logger;
27 | }
28 |
29 | [TempData]
30 | public string StatusMessage { get; set; }
31 |
32 | public async Task OnGet()
33 | {
34 | var user = await _userManager.GetUserAsync(User);
35 | if (user == null)
36 | {
37 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
38 | }
39 |
40 | return Page();
41 | }
42 |
43 | public async Task OnPostAsync()
44 | {
45 | var user = await _userManager.GetUserAsync(User);
46 | if (user == null)
47 | {
48 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
49 | }
50 |
51 | await _userManager.SetTwoFactorEnabledAsync(user, false);
52 | await _userManager.ResetAuthenticatorKeyAsync(user);
53 | _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id);
54 |
55 | await _signInManager.RefreshSignInAsync(user);
56 | StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.";
57 |
58 | return RedirectToPage("./EnableAuthenticator");
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model SetPasswordModel
3 | @{
4 | ViewData["Title"] = "Set password";
5 | ViewData["ActivePage"] = ManageNavPages.ChangePassword;
6 | }
7 |
8 |
Set your password
9 |
10 |
11 | You do not have a local username/password for this site. Add a local
12 | account so you can log in without an external login.
13 |
14 |
15 |
16 |
30 |
31 |
32 |
33 | @section Scripts {
34 |
35 | }
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using UserManagementReact.Entities;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 |
11 | namespace UserManagementReact.Areas.Identity.Pages.Account.Manage
12 | {
13 | public class SetPasswordModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly SignInManager _signInManager;
17 |
18 | public SetPasswordModel(
19 | UserManager userManager,
20 | SignInManager signInManager)
21 | {
22 | _userManager = userManager;
23 | _signInManager = signInManager;
24 | }
25 |
26 | [BindProperty]
27 | public InputModel Input { get; set; }
28 |
29 | [TempData]
30 | public string StatusMessage { get; set; }
31 |
32 | public class InputModel
33 | {
34 | [Required]
35 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
36 | [DataType(DataType.Password)]
37 | [Display(Name = "New password")]
38 | public string NewPassword { get; set; }
39 |
40 | [DataType(DataType.Password)]
41 | [Display(Name = "Confirm new password")]
42 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
43 | public string ConfirmPassword { get; set; }
44 | }
45 |
46 | public async Task OnGetAsync()
47 | {
48 | var user = await _userManager.GetUserAsync(User);
49 | if (user == null)
50 | {
51 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
52 | }
53 |
54 | var hasPassword = await _userManager.HasPasswordAsync(user);
55 |
56 | if (hasPassword)
57 | {
58 | return RedirectToPage("./ChangePassword");
59 | }
60 |
61 | return Page();
62 | }
63 |
64 | public async Task OnPostAsync()
65 | {
66 | if (!ModelState.IsValid)
67 | {
68 | return Page();
69 | }
70 |
71 | var user = await _userManager.GetUserAsync(User);
72 | if (user == null)
73 | {
74 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
75 | }
76 |
77 | var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);
78 | if (!addPasswordResult.Succeeded)
79 | {
80 | foreach (var error in addPasswordResult.Errors)
81 | {
82 | ModelState.AddModelError(string.Empty, error.Description);
83 | }
84 | return Page();
85 | }
86 |
87 | await _signInManager.RefreshSignInAsync(user);
88 | StatusMessage = "Your password has been set.";
89 |
90 | return RedirectToPage();
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/UserManagementReact/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ShowRecoveryCodesModel
3 | @{
4 | ViewData["Title"] = "Recovery codes";
5 | ViewData["ActivePage"] = "TwoFactorAuthentication";
6 | }
7 |
8 |
9 |
@ViewData["Title"]
10 |
11 |
12 | Put these codes in a safe place.
13 |
14 |
15 | If you lose your device and don't have the recovery codes you will lose access to your account.
16 |
19 | Swapping to Development environment will display more detailed information about the error that occurred.
20 |
21 |
22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application.
23 |
Client-side navigation. For example, click Counter then Back to return here.
19 |
Development server integration. In development mode, the development server from create-react-app runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.
20 |
Efficient production builds. In production mode, development-time features are disabled, and your dotnet publish configuration produces minified, efficiently bundled JavaScript files.
21 |
22 |
The ClientApp subdirectory is a standard React application based on the create-react-app template. If you open a command prompt in that directory, you can run npm commands such as npm test or npm install.
81 | );
82 | }
83 |
84 | async retrieveFormData() {
85 | const data = await usersService.getUser(this.userId);
86 | this.setState({ user: data, loading: false });
87 | }
88 | }
89 |
90 | export const UsersEdit = withTranslation()(withRouter(UsersEditPlain));
91 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/components/users/UsersPasswordChange.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import usersService from './UsersService';
3 | import { AvForm, AvField } from 'availity-reactstrap-validation';
4 | import { FormGroup, Form, Label, Input, Button } from 'reactstrap';
5 | import { withTranslation } from 'react-i18next';
6 |
7 | class UsersPasswordChangePlain extends Component {
8 |
9 | constructor(props) {
10 | super(props);
11 |
12 | const { match } = this.props;
13 | this.userId = match.params.userId;
14 | }
15 |
16 | componentDidMount() {
17 | }
18 |
19 | handleClickCancel = () => {
20 | const { history } = this.props;
21 |
22 | history.push('/users');
23 | }
24 |
25 | handleValidSubmit = (event, values) => {
26 | const { history } = this.props;
27 |
28 | (async () => {
29 | await usersService.changeUserPassword(this.userId, values);
30 | history.push('/users');
31 | })();
32 | }
33 |
34 | render() {
35 | const { t, i18n } = this.props;
36 | return (
37 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | export const UsersPasswordChange = withTranslation()(UsersPasswordChangePlain);
52 |
53 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/components/users/UsersRouter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route, useRouteMatch } from 'react-router-dom';
3 | import AuthorizeRoute from '../api-authorization/AuthorizeRoute';
4 |
5 | import { UsersList } from './UsersList';
6 | import { UsersAdd } from './UsersAdd';
7 | import { UsersDelete } from './UsersDelete';
8 | import { UsersEdit } from './UsersEdit';
9 | import { UsersPasswordChange } from './UsersPasswordChange';
10 |
11 | export function UsersRouter() {
12 | let match = useRouteMatch();
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/custom.css:
--------------------------------------------------------------------------------
1 | /* Provide sufficient contrast against white background */
2 | a {
3 | color: #0366d6;
4 | }
5 |
6 | code {
7 | color: #E01A76;
8 | }
9 |
10 | .btn-primary {
11 | color: #fff;
12 | background-color: #1b6ec2;
13 | border-color: #1861ac;
14 | }
15 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/i18n.js:
--------------------------------------------------------------------------------
1 | import i18n from "i18next";
2 | import { initReactI18next } from "react-i18next";
3 | import translations from "./translations.js";
4 |
5 | // the translations
6 | // (tip move them in a JSON file and import them)
7 | //const resources = {
8 | // en: {
9 | // translation: {
10 | // "Welcome to React": "Welcome to React and react-i18next",
11 | // "Hello": "Hi there!",
12 | // "Logout": "Get out of here"
13 | // }
14 | // },
15 | // el: {
16 | // translation: {
17 | // "Hello": "Γεια σου!",
18 | // "Logout": "Αποσύνδεση"
19 | // }
20 | // }
21 | //};
22 |
23 | const resources = translations;
24 |
25 | i18n
26 | .use(initReactI18next) // passes i18n down to react-i18next
27 | .init({
28 | resources,
29 | lng: "en",
30 |
31 | keySeparator: false, // we do not use keys in form messages.welcome
32 |
33 | interpolation: {
34 | escapeValue: false // react already safes from xss
35 | }
36 | });
37 |
38 | export default i18n;
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/index.js:
--------------------------------------------------------------------------------
1 | import 'bootstrap/dist/css/bootstrap.css';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import { BrowserRouter } from 'react-router-dom';
5 | import './i18n';
6 | import App from './App';
7 | //import registerServiceWorker from './registerServiceWorker';
8 |
9 | const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
10 | const rootElement = document.getElementById('root');
11 |
12 | ReactDOM.render(
13 |
14 |
15 | ,
16 | rootElement);
17 |
18 | // Uncomment the line above that imports the registerServiceWorker function
19 | // and the line below to register the generated service worker.
20 | // By default create-react-app includes a service worker to improve the
21 | // performance of the application by caching static assets. This service
22 | // worker can interfere with the Identity UI, so it is
23 | // disabled by default when Identity is being used.
24 | //
25 | //registerServiceWorker();
26 |
27 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register () {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 | } else {
39 | // Is not local host. Just register service worker
40 | registerValidSW(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW (swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker (swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister () {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/setupTests.js:
--------------------------------------------------------------------------------
1 | const localStorageMock = {
2 | getItem: jest.fn(),
3 | setItem: jest.fn(),
4 | removeItem: jest.fn(),
5 | clear: jest.fn(),
6 | };
7 | global.localStorage = localStorageMock;
8 |
9 | // Mock the request issued by the react app to get the client configuration parameters.
10 | window.fetch = () => {
11 | return Promise.resolve(
12 | {
13 | ok: true,
14 | json: () => Promise.resolve({
15 | "authority": "https://localhost:5001",
16 | "client_id": "UserManagementReact",
17 | "redirect_uri": "https://localhost:5001/authentication/login-callback",
18 | "post_logout_redirect_uri": "https://localhost:5001/authentication/logout-callback",
19 | "response_type": "id_token token",
20 | "scope": "UserManagementReactAPI openid profile"
21 | })
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/UserManagementReact/ClientApp/src/translations.js:
--------------------------------------------------------------------------------
1 | export default
2 | {
3 | "en": {
4 | "translation": {
5 | "en": "English",
6 | "Welcome to React": "Welcome to React and react-i18next",
7 | "FieldRequired": "The field is required!",
8 | "FieldInvalid": "The field is invalid!",
9 | "FirstName": "First name"
10 | }
11 | },
12 | "el": {
13 | "translation": {
14 | "Home": "Αρχική",
15 | "Counter": "Μετρητής",
16 | "Fetch data": "Κατέβασε δεδομένα",
17 | "el": "Ελληνικά",
18 | "Hello": "Γεια σου!",
19 | "Logout": "Αποσύνδεση",
20 | "Users": "Χρήστες",
21 | "FieldRequired": "Το πεδίο είναι απαραίτητο!",
22 | "FieldInvalid": "Το πεδίο είναι λανθασμένο!",
23 | "FirstName": "Όνομα"
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/UserManagementReact/Controllers/ConfigController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using AutoMapper;
6 | using UserManagementReact.Entities;
7 | using UserManagementReact.Models;
8 | using UserManagementReact.Services;
9 | using UserManagementReact.Services.Helpers;
10 | using Microsoft.AspNetCore.Authorization;
11 | using Microsoft.AspNetCore.Http;
12 | using Microsoft.AspNetCore.Identity;
13 | using Microsoft.AspNetCore.Localization;
14 | using Microsoft.AspNetCore.Mvc;
15 | using Microsoft.EntityFrameworkCore;
16 | using Microsoft.Extensions.Configuration;
17 | using Microsoft.Extensions.Logging;
18 |
19 | namespace UserManagementReact.Controllers
20 | {
21 | [Route("Config")]
22 | public class ConfigController : ControllerBase
23 | {
24 | private readonly ILogger _logger;
25 | private readonly IConfiguration _configuration;
26 |
27 | public ConfigController(
28 | ILogger logger,
29 | IConfiguration configuration)
30 | {
31 | _logger = logger;
32 | _configuration = configuration;
33 | }
34 |
35 | [HttpPost]
36 | public IActionResult SetLanguage(string culture, string returnUrl)
37 | {
38 | Response.Cookies.Append(
39 | CookieRequestCultureProvider.DefaultCookieName,
40 | CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
41 | new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
42 | );
43 |
44 | return LocalRedirect(returnUrl);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/UserManagementReact/Controllers/OidcConfigurationController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.Extensions.Logging;
4 |
5 | namespace UserManagementReact.Controllers
6 | {
7 | public class OidcConfigurationController : Controller
8 | {
9 | private readonly ILogger _logger;
10 |
11 | public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger logger)
12 | {
13 | ClientRequestParametersProvider = clientRequestParametersProvider;
14 | _logger = logger;
15 | }
16 |
17 | public IClientRequestParametersProvider ClientRequestParametersProvider { get; }
18 |
19 | [HttpGet("_configuration/{clientId}")]
20 | public IActionResult GetClientRequestParameters([FromRoute]string clientId)
21 | {
22 | var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);
23 | return Ok(parameters);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/UserManagementReact/Controllers/WeatherForecastController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Authorization;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace UserManagementReact.Controllers
10 | {
11 | [Authorize]
12 | [ApiController]
13 | [Route("api/[controller]")]
14 | public class WeatherForecastController : ControllerBase
15 | {
16 | private static readonly string[] Summaries = new[]
17 | {
18 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
19 | };
20 |
21 | private readonly ILogger _logger;
22 |
23 | public WeatherForecastController(ILogger logger)
24 | {
25 | _logger = logger;
26 | }
27 |
28 | [HttpGet]
29 | public IEnumerable Get()
30 | {
31 | var rng = new Random();
32 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast
33 | {
34 | Date = DateTime.Now.AddDays(index),
35 | TemperatureC = rng.Next(-20, 55),
36 | Summary = Summaries[rng.Next(Summaries.Length)]
37 | })
38 | .ToArray();
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/UserManagementReact/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "", Scope = "member", Target = "~M:UserManagementReact.Areas.Identity.Pages.Account.Manage.ChangePasswordModel.OnPostAsync~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")]
7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:UserManagementReact.Areas.Identity.Pages.Account.ConfirmEmailModel.OnGetAsync(System.String,System.String)~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")]
8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:UserManagementReact.Areas.Identity.Pages.Account.ExternalLoginModel.OnPostConfirmationAsync(System.String)~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")]
9 |
--------------------------------------------------------------------------------
/UserManagementReact/Helpers/AutomapperProfiles.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using UserManagementReact.Entities;
3 | using UserManagementReact.Models;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | namespace UserManagementReact.Helpers
10 | {
11 | public class AutomapperProfiles
12 | {
13 | public class UserProfile : Profile
14 | {
15 | public UserProfile()
16 | {
17 | CreateMap()
18 | .ForMember(dest => dest.Password, opt => opt.Ignore())
19 | .ForMember(dest => dest.ConfirmPassword, opt => opt.Ignore());
20 |
21 | // Make sure to not ovewrite automatically created ApplicationUser Id when ApplicationUserViewModel Id is null
22 | CreateMap()
23 | .ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.Email))
24 | .ForMember(dest => dest.Id, opt => opt.Condition(cond => cond.Id != null));
25 |
26 | CreateMap();
27 |
28 | CreateMap()
29 | .ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.Email));
30 |
31 | //CreateMap();
32 |
33 | //CreateMap()
34 | // .ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.Email));
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/UserManagementReact/Helpers/Cultures.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Globalization;
3 |
4 | namespace UserManagementReact.Helpers
5 | {
6 | public static class Cultures
7 | {
8 | public struct CulturePair
9 | {
10 | public string Code { get; set; }
11 | public string LocalName { get; set; }
12 | }
13 | public static List CulturePairs = new List()
14 | {
15 | new CulturePair() { Code = "en", LocalName = "English" },
16 | new CulturePair() { Code = "el", LocalName = "Ελληνικά" }
17 | };
18 |
19 | public static string DefaultCulture = "el";
20 |
21 | public static List SupportedCultures
22 | {
23 | get
24 | {
25 | List returnList = new List();
26 |
27 | foreach (CulturePair culturePair in CulturePairs)
28 | {
29 | returnList.Add(new CultureInfo(culturePair.Code));
30 | }
31 | return returnList;
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/UserManagementReact/Helpers/ProfileService.cs:
--------------------------------------------------------------------------------
1 | using UserManagementReact.Services;
2 | using IdentityModel;
3 | using IdentityServer4.Models;
4 | using IdentityServer4.Services;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Security.Claims;
9 | using System.Threading.Tasks;
10 |
11 | namespace UserManagementReact.Helpers
12 | {
13 | public class ProfileService : IProfileService
14 | {
15 | private readonly IUserManagementService _umService;
16 |
17 | public ProfileService(IUserManagementService umService)
18 | {
19 | _umService = umService;
20 | }
21 |
22 | public async Task GetProfileDataAsync(ProfileDataRequestContext context)
23 | {
24 | if (context == null)
25 | return;
26 |
27 | var claims = context.Subject.Claims
28 | .Where(claim => claim.Type == JwtClaimTypes.Email || claim.Type == JwtClaimTypes.Role).ToList();
29 |
30 | //foreach (var claim in claims)
31 | //{
32 | // if (claim.Type == "role")
33 | // context.IssuedClaims.Add(new Claim(ClaimTypes.Role, claim.Value));
34 | //}
35 |
36 | context.IssuedClaims.AddRange(claims);
37 | }
38 |
39 | public async Task IsActiveAsync(IsActiveContext context)
40 | {
41 | var user = await _umService.GetUserAsync(context.Subject);
42 | context.IsActive = (user != null);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/UserManagementReact/Helpers/SharedResources.cs:
--------------------------------------------------------------------------------
1 | namespace UserManagementReact.Shared
2 | {
3 | public class SharedResources
4 | {
5 | }
6 | }
--------------------------------------------------------------------------------
/UserManagementReact/Models/PageUsersModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace UserManagementReact.Models
7 | {
8 | public class PageUsersModel
9 | {
10 | public int TotalUsers { get; set; }
11 | public List Users { get; set; }
12 | = new List();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/UserManagementReact/Models/PasswordChangeModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserManagementReact.Models
8 | {
9 | public class PasswordChangeModel
10 | {
11 | [StringLength(100, MinimumLength = 1)]
12 | public string Password { get; set; }
13 |
14 | [Compare("Password")]
15 | public string ConfirmPassword { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/UserManagementReact/Models/UserModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserManagementReact.Models
8 | {
9 | public class UserModel
10 | {
11 | public byte[] RowVersion { get; set; }
12 |
13 | public string Id { get; set; }
14 |
15 | [StringLength(50)]
16 | public string FirstName { get; set; }
17 |
18 | [StringLength(50)]
19 | public string LastName { get; set; }
20 |
21 | [Required()]
22 | [EmailAddress()]
23 | public string Email { get; set; }
24 |
25 | [Required()]
26 | [StringLength(100, MinimumLength = 1)]
27 | public string Password { get; set; }
28 |
29 | [Compare("Password")]
30 | public string ConfirmPassword { get; set; }
31 |
32 | [Required]
33 | public string Role { get; set; }
34 |
35 | [Required]
36 | public bool Approved { get; set; }
37 | }
38 |
39 | public class UpdateUserModel
40 | {
41 | public byte[] RowVersion { get; set; }
42 |
43 | public string Id { get; set; }
44 |
45 | [StringLength(50)]
46 | public string FirstName { get; set; }
47 |
48 | [StringLength(50)]
49 | public string LastName { get; set; }
50 |
51 | [Required()]
52 | [EmailAddress()]
53 | public string Email { get; set; }
54 |
55 | [Required]
56 | public string Role { get; set; }
57 |
58 | [Required]
59 | public bool Approved { get; set; }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/UserManagementReact/Pages/Error.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ErrorModel
3 | @{
4 | ViewData["Title"] = "Error";
5 | }
6 |
7 |
Error.
8 |
An error occurred while processing your request.
9 |
10 | @if (Model.ShowRequestId)
11 | {
12 |
13 | Request ID:@Model.RequestId
14 |
15 | }
16 |
17 |
Development Mode
18 |
19 | Swapping to the Development environment displays detailed information about the error that occurred.
20 |
21 |
22 | The Development environment shouldn't be enabled for deployed applications.
23 | It can result in displaying sensitive information from exceptions to end users.
24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
25 | and restarting the app.
26 |