Merge branch 'feature/MoveUserUI' into develop

This commit is contained in:
Samuele Locatelli
2021-08-25 16:32:22 +02:00
150 changed files with 41994 additions and 63 deletions
+23
View File
@@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GWMS.Data.DatabaseModels
{
/// <summary>
/// Classe fake x il conteggio tabelle e check preliminari
/// </summary>
[Keyless]
public class TableCount
{
#region Public Properties
public int Count { get; set; }
public string TableName { get; set; }
#endregion Public Properties
}
}
+6
View File
@@ -41,6 +41,12 @@ namespace GWMS.Data
#endregion Public Constructors
#region Public Properties
public DbSet<TableCount> DbSetCounts { get; set; }
#endregion Public Properties
#region Protected Methods
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+12 -10
View File
@@ -1,10 +1,12 @@
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
+10
View File
@@ -0,0 +1,10 @@
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
@@ -0,0 +1,27 @@
using System;
using GWMS.Data;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
[assembly: HostingStartup(typeof(GWMS.UI.Areas.Identity.IdentityHostingStartup))]
namespace GWMS.UI.Areas.Identity
{
public class IdentityHostingStartup : IHostingStartup
{
#region Public Methods
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) =>
{
});
}
#endregion Public Methods
}
}
@@ -0,0 +1,10 @@
@page
@model AccessDeniedModel
@{
ViewData["Title"] = "Access denied";
}
<header>
<h1 class="text-danger">@ViewData["Title"]</h1>
<p class="text-danger">You do not have access to this resource.</p>
</header>
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
public class AccessDeniedModel : PageModel
{
#region Public Methods
public void OnGet()
{
}
#endregion Public Methods
}
}
@@ -0,0 +1,7 @@
@page
@model ConfirmEmailModel
@{
ViewData["Title"] = "Confirm email";
}
<h1>@ViewData["Title"]</h1>
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ConfirmEmailModel : PageModel
{
#region Private Fields
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public ConfirmEmailModel(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
#endregion Public Constructors
#region Public Properties
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGetAsync(string userId, string code)
{
if (userId == null || code == null)
{
return RedirectToPage("/Index");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound($"Unable to load user with ID '{userId}'.");
}
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await _userManager.ConfirmEmailAsync(user, code);
StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
return Page();
}
#endregion Public Methods
}
}
@@ -0,0 +1,8 @@
@page
@model ConfirmEmailChangeModel
@{
ViewData["Title"] = "Confirm email change";
}
<h1>@ViewData["Title"]</h1>
<partial name="_StatusMessage" model="Model.StatusMessage" />
@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ConfirmEmailChangeModel : PageModel
{
#region Private Fields
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public ConfirmEmailChangeModel(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
#endregion Public Constructors
#region Public Properties
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGetAsync(string userId, string email, string code)
{
if (userId == null || email == null || code == null)
{
return RedirectToPage("/Index");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound($"Unable to load user with ID '{userId}'.");
}
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await _userManager.ChangeEmailAsync(user, email, code);
if (!result.Succeeded)
{
StatusMessage = "Error changing email.";
return Page();
}
// In our UI email and user name are one and the same, so when we update the email
// we need to update the user name.
var setUserNameResult = await _userManager.SetUserNameAsync(user, email);
if (!setUserNameResult.Succeeded)
{
StatusMessage = "Error changing user name.";
return Page();
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Thank you for confirming your email change.";
return Page();
}
#endregion Public Methods
}
}
@@ -0,0 +1,33 @@
@page
@model ExternalLoginModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<h4 id="external-login-title">Associate your @Model.ProviderDisplayName account.</h4>
<hr />
<p id="external-login-description" class="text-info">
You've successfully authenticated with <strong>@Model.ProviderDisplayName</strong>.
Please enter an email address for this site below and click the Register button to finish
logging in.
</p>
<div class="row">
<div class="col-md-4">
<form asp-page-handler="Confirmation" asp-route-returnUrl="@Model.ReturnUrl" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Claims;
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;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ExternalLoginModel : PageModel
{
#region Private Fields
private readonly IEmailSender _emailSender;
private readonly ILogger<ExternalLoginModel> _logger;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public ExternalLoginModel(
SignInManager<IdentityUser> signInManager,
UserManager<IdentityUser> userManager,
ILogger<ExternalLoginModel> logger,
IEmailSender emailSender)
{
_signInManager = signInManager;
_userManager = userManager;
_logger = logger;
_emailSender = emailSender;
}
#endregion Public Constructors
#region Public Properties
[TempData]
public string ErrorMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public string ProviderDisplayName { get; set; }
public string ReturnUrl { get; set; }
#endregion Public Properties
#region Public Methods
public IActionResult OnGetAsync()
{
return RedirectToPage("./Login");
}
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ReturnUrl = returnUrl;
ProviderDisplayName = info.ProviderDisplayName;
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input = new InputModel
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
}
return Page();
}
}
public IActionResult OnPost(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
// If account confirmation is required, we need to show the link if we don't have a real email sender
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
}
await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
ProviderDisplayName = info.ProviderDisplayName;
ReturnUrl = returnUrl;
return Page();
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[Required]
[EmailAddress]
public string Email { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,26 @@
@page
@model ForgotPasswordModel
@{
ViewData["Title"] = "Forgot your password?";
}
<h1>@ViewData["Title"]</h1>
<h4>Enter your email.</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Encodings.Web;
using System.Text;
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 GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ForgotPasswordModel : PageModel
{
#region Private Fields
private readonly IEmailSender _emailSender;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public ForgotPasswordModel(UserManager<IdentityUser> userManager, IEmailSender emailSender)
{
_userManager = userManager;
_emailSender = emailSender;
}
#endregion Public Constructors
#region Public Properties
[BindProperty]
public InputModel Input { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnPostAsync()
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
{
// Don't reveal that the user does not exist or is not confirmed
return RedirectToPage("./ForgotPasswordConfirmation");
}
// For more information on how to enable account confirmation and password reset please
// visit https://go.microsoft.com/fwlink/?LinkID=532713
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ResetPassword",
pageHandler: null,
values: new { area = "Identity", code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
Input.Email,
"Reset Password",
$"Please reset your password by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
return RedirectToPage("./ForgotPasswordConfirmation");
}
return Page();
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[Required]
[EmailAddress]
public string Email { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,11 @@
@page
@model ForgotPasswordConfirmation
@{
ViewData["Title"] = "Forgot password confirmation";
}
<h1>@ViewData["Title"]</h1>
<p>
Please check your email to reset your password.
</p>
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ForgotPasswordConfirmation : PageModel
{
#region Public Methods
public void OnGet()
{
}
#endregion Public Methods
}
}
@@ -0,0 +1,10 @@
@page
@model LockoutModel
@{
ViewData["Title"] = "Locked out";
}
<header>
<h1 class="text-danger">@ViewData["Title"]</h1>
<p class="text-danger">This account has been locked out, please try again later.</p>
</header>
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class LockoutModel : PageModel
{
#region Public Methods
public void OnGet()
{
}
#endregion Public Methods
}
}
@@ -1,15 +1,21 @@
@page
@using Microsoft.AspNetCore.Identity
@attribute [IgnoreAntiforgeryToken]
@inject SignInManager<IdentityUser> SignInManager
@functions {
public async Task<IActionResult> OnPost()
{
if (SignInManager.IsSignedIn(User))
{
await SignInManager.SignOutAsync();
}
return Redirect("~/");
}
@model LogoutModel
@{
ViewData["Title"] = "Log out";
}
<header>
<h1>@ViewData["Title"]</h1>
@{
if (User.Identity.IsAuthenticated)
{
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post">
<button type="submit" class="nav-link btn btn-warning">Click here to Logout</button>
</form>
}
else
{
<p>You have successfully logged out of the application.</p>
}
}
</header>
@@ -0,0 +1,85 @@
@page
@model LoginModel
@{
ViewData["Title"] = "Log in";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-6">
<section>
<form id="account" method="post">
<h4>Use a local account to log in.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="Input.RememberMe">
<input asp-for="Input.RememberMe" />
@Html.DisplayNameFor(m => m.Input.RememberMe)
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Log in</button>
</div>
<div class="form-group">
<p>
<a id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a>
</p>
<p>
<a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
</p>
<p>
<a id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a>
</p>
</div>
</form>
</section>
</div>
@*<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to log in.</h4>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>*@
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class LoginModel : PageModel
{
#region Private Fields
private readonly ILogger<LoginModel> _logger;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public LoginModel(SignInManager<IdentityUser> signInManager,
ILogger<LoginModel> logger,
UserManager<IdentityUser> userManager)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Properties
[TempData]
public string ErrorMessage { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
#endregion Public Properties
#region Private Methods
private async Task CheckSuperAdmin()
{
// se non ci fosse --> creo samuele come superadmin
string superUser = "samuele@steamware.net";
string superPwd = "viaDante16!";
string ADMIN_ROLE = "SuperAdmin";
var user = await _userManager.FindByEmailAsync(superUser);
if (user == null)
{
// Insert new user
var NewUser =
new IdentityUser
{
UserName = superUser,
Email = superUser,
EmailConfirmed = true
};
var CreateResult = await _userManager.CreateAsync(NewUser, superPwd);
if (CreateResult.Succeeded)
{
user = await _userManager.FindByEmailAsync(superUser);
}
}
// verifico ruoli...
if (user != null)
{
// Gestione salvataggio ruoli... SE VARIATO...
var UserRoles = await _userManager.GetRolesAsync(user);
if (UserRoles != null && UserRoles.Count > 0)
{
var oldRole = UserRoles.Where(x => x == ADMIN_ROLE).FirstOrDefault();
if (oldRole == null)
{
// aggiungo a ruolo admin
await _userManager.AddToRoleAsync(user, ADMIN_ROLE);
}
}
}
}
#endregion Private Methods
#region Public Methods
public async Task OnGetAsync(string returnUrl = null)
{
CheckSuperAdmin();
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}
returnUrl ??= Url.Content("~/");
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,41 @@
@page
@model LoginWith2faModel
@{
ViewData["Title"] = "Two-factor authentication";
}
<h1>@ViewData["Title"]</h1>
<hr />
<p>Your login is protected with an authenticator app. Enter your authenticator code below.</p>
<div class="row">
<div class="col-md-4">
<form method="post" asp-route-returnUrl="@Model.ReturnUrl">
<input asp-for="RememberMe" type="hidden" />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.TwoFactorCode"></label>
<input asp-for="Input.TwoFactorCode" class="form-control" autocomplete="off" />
<span asp-validation-for="Input.TwoFactorCode" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="Input.RememberMachine">
<input asp-for="Input.RememberMachine" />
@Html.DisplayNameFor(m => m.Input.RememberMachine)
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Log in</button>
</div>
</form>
</div>
</div>
<p>
Don't have access to your authenticator device? You can
<a id="recovery-code-login" asp-page="./LoginWithRecoveryCode" asp-route-returnUrl="@Model.ReturnUrl">log in with a recovery code</a>.
</p>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class LoginWith2faModel : PageModel
{
#region Private Fields
private readonly ILogger<LoginWith2faModel> _logger;
private readonly SignInManager<IdentityUser> _signInManager;
#endregion Private Fields
#region Public Constructors
public LoginWith2faModel(SignInManager<IdentityUser> signInManager, ILogger<LoginWith2faModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Properties
[BindProperty]
public InputModel Input { get; set; }
public bool RememberMe { get; set; }
public string ReturnUrl { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGetAsync(bool rememberMe, string returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
}
ReturnUrl = returnUrl;
RememberMe = rememberMe;
return Page();
}
public async Task<IActionResult> OnPostAsync(bool rememberMe, string returnUrl = null)
{
if (!ModelState.IsValid)
{
return Page();
}
returnUrl = returnUrl ?? Url.Content("~/");
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
}
var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
if (result.Succeeded)
{
_logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
return LocalRedirect(returnUrl);
}
else if (result.IsLockedOut)
{
_logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
return RedirectToPage("./Lockout");
}
else
{
_logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
return Page();
}
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[Display(Name = "Remember this machine")]
public bool RememberMachine { get; set; }
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Authenticator code")]
public string TwoFactorCode { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,29 @@
@page
@model LoginWithRecoveryCodeModel
@{
ViewData["Title"] = "Recovery code verification";
}
<h1>@ViewData["Title"]</h1>
<hr />
<p>
You have requested to log in with a recovery code. This login will not be remembered until you provide
an authenticator app code at log in or disable 2FA and log in again.
</p>
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.RecoveryCode"></label>
<input asp-for="Input.RecoveryCode" class="form-control" autocomplete="off" />
<span asp-validation-for="Input.RecoveryCode" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Log in</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class LoginWithRecoveryCodeModel : PageModel
{
#region Private Fields
private readonly ILogger<LoginWithRecoveryCodeModel> _logger;
private readonly SignInManager<IdentityUser> _signInManager;
#endregion Private Fields
#region Public Constructors
public LoginWithRecoveryCodeModel(SignInManager<IdentityUser> signInManager, ILogger<LoginWithRecoveryCodeModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Properties
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGetAsync(string returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
}
ReturnUrl = returnUrl;
return Page();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
}
var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
if (result.Succeeded)
{
_logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
return LocalRedirect(returnUrl ?? Url.Content("~/"));
}
if (result.IsLockedOut)
{
_logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
return RedirectToPage("./Lockout");
}
else
{
_logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
return Page();
}
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[BindProperty]
[Required]
[DataType(DataType.Text)]
[Display(Name = "Recovery Code")]
public string RecoveryCode { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class LogoutModel : PageModel
{
#region Private Fields
private readonly ILogger<LogoutModel> _logger;
private readonly SignInManager<IdentityUser> _signInManager;
#endregion Private Fields
#region Public Constructors
public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Methods
public void OnGet()
{
}
public async Task<IActionResult> OnPost(string returnUrl = null)
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
if (returnUrl != null)
{
return LocalRedirect(returnUrl);
}
else
{
return RedirectToPage();
}
}
#endregion Public Methods
}
}
@@ -0,0 +1,36 @@
@page
@model ChangePasswordModel
@{
ViewData["Title"] = "Change password";
ViewData["ActivePage"] = ManageNavPages.ChangePassword;
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="change-password-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.OldPassword"></label>
<input asp-for="Input.OldPassword" class="form-control" />
<span asp-validation-for="Input.OldPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.NewPassword"></label>
<input asp-for="Input.NewPassword" class="form-control" />
<span asp-validation-for="Input.NewPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Update password</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class ChangePasswordModel : PageModel
{
#region Private Fields
private readonly ILogger<ChangePasswordModel> _logger;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public ChangePasswordModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<ChangePasswordModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Properties
[BindProperty]
public InputModel Input { get; set; }
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (!hasPassword)
{
return RedirectToPage("./SetPassword");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
if (!changePasswordResult.Succeeded)
{
foreach (var error in changePasswordResult.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
await _signInManager.RefreshSignInAsync(user);
_logger.LogInformation("User changed their password successfully.");
StatusMessage = "Your password has been changed.";
return RedirectToPage();
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Current password")]
public string OldPassword { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,33 @@
@page
@model DeletePersonalDataModel
@{
ViewData["Title"] = "Delete Personal Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}
<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
<p>
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
</p>
</div>
<div>
<form id="delete-user" method="post" class="form-group">
<div asp-validation-summary="All" class="text-danger"></div>
@if (Model.RequirePassword)
{
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
}
<button class="btn btn-danger" type="submit">Delete data and close my account</button>
</form>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,107 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class DeletePersonalDataModel : PageModel
{
#region Private Fields
private readonly ILogger<DeletePersonalDataModel> _logger;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public DeletePersonalDataModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<DeletePersonalDataModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Properties
[BindProperty]
public InputModel Input { get; set; }
public bool RequirePassword { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
RequirePassword = await _userManager.HasPasswordAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
RequirePassword = await _userManager.HasPasswordAsync(user);
if (RequirePassword)
{
if (!await _userManager.CheckPasswordAsync(user, Input.Password))
{
ModelState.AddModelError(string.Empty, "Incorrect password.");
return Page();
}
}
var result = await _userManager.DeleteAsync(user);
var userId = await _userManager.GetUserIdAsync(user);
if (!result.Succeeded)
{
throw new InvalidOperationException($"Unexpected error occurred deleting user with ID '{userId}'.");
}
await _signInManager.SignOutAsync();
_logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
return Redirect("~/");
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,25 @@
@page
@model Disable2faModel
@{
ViewData["Title"] = "Disable two-factor authentication (2FA)";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}
<partial name="_StatusMessage" for="StatusMessage" />
<h2>@ViewData["Title"]</h2>
<div class="alert alert-warning" role="alert">
<p>
<strong>This action only disables 2FA.</strong>
</p>
<p>
Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
</p>
</div>
<div>
<form method="post" class="form-group">
<button class="btn btn-danger" type="submit">Disable 2FA</button>
</form>
</div>
@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class Disable2faModel : PageModel
{
#region Private Fields
private readonly ILogger<Disable2faModel> _logger;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public Disable2faModel(
UserManager<IdentityUser> userManager,
ILogger<Disable2faModel> logger)
{
_userManager = userManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Properties
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!await _userManager.GetTwoFactorEnabledAsync(user))
{
throw new InvalidOperationException($"Cannot disable 2FA for user with ID '{_userManager.GetUserId(User)}' as it's not currently enabled.");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
if (!disable2faResult.Succeeded)
{
throw new InvalidOperationException($"Unexpected error occurred disabling 2FA for user with ID '{_userManager.GetUserId(User)}'.");
}
_logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User));
StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app";
return RedirectToPage("./TwoFactorAuthentication");
}
#endregion Public Methods
}
}
@@ -0,0 +1,12 @@
@page
@model DownloadPersonalDataModel
@{
ViewData["Title"] = "Download Your Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}
<h4>@ViewData["Title"]</h4>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class DownloadPersonalDataModel : PageModel
{
#region Private Fields
private readonly ILogger<DownloadPersonalDataModel> _logger;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public DownloadPersonalDataModel(
UserManager<IdentityUser> userManager,
ILogger<DownloadPersonalDataModel> logger)
{
_userManager = userManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Methods
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
_logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User));
// Only include personal data for download
var personalData = new Dictionary<string, string>();
var personalDataProps = typeof(IdentityUser).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
foreach (var p in personalDataProps)
{
personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
}
var logins = await _userManager.GetLoginsAsync(user);
foreach (var l in logins)
{
personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey);
}
Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json");
return new FileContentResult(JsonSerializer.SerializeToUtf8Bytes(personalData), "application/json");
}
#endregion Public Methods
}
}
@@ -0,0 +1,43 @@
@page
@model EmailModel
@{
ViewData["Title"] = "Manage Email";
ViewData["ActivePage"] = ManageNavPages.Email;
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" model="Model.StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="email-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
@if (Model.IsEmailConfirmed)
{
<div class="input-group">
<input asp-for="Email" class="form-control" disabled />
<div class="input-group-append">
<span class="input-group-text text-success font-weight-bold">✓</span>
</div>
</div>
}
else
{
<input asp-for="Email" class="form-control" disabled />
<button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
}
</div>
<div class="form-group">
<label asp-for="Input.NewEmail"></label>
<input asp-for="Input.NewEmail" class="form-control" />
<span asp-validation-for="Input.NewEmail" class="text-danger"></span>
</div>
<button id="change-email-button" type="submit" asp-page-handler="ChangeEmail" class="btn btn-primary">Change email</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
using System.Text.Encodings.Web;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public partial class EmailModel : PageModel
{
#region Private Fields
private readonly IEmailSender _emailSender;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public EmailModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
}
#endregion Public Constructors
#region Public Properties
public string Email { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public bool IsEmailConfirmed { get; set; }
[TempData]
public string StatusMessage { get; set; }
public string Username { get; set; }
#endregion Public Properties
#region Private Methods
private async Task LoadAsync(IdentityUser user)
{
var email = await _userManager.GetEmailAsync(user);
Email = email;
Input = new InputModel
{
NewEmail = email,
};
IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
}
#endregion Private Methods
#region Public Methods
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadAsync(user);
return Page();
}
public async Task<IActionResult> OnPostChangeEmailAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var email = await _userManager.GetEmailAsync(user);
if (Input.NewEmail != email)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmailChange",
pageHandler: null,
values: new { userId = userId, email = Input.NewEmail, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
Input.NewEmail,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
StatusMessage = "Confirmation link to change email sent. Please check your email.";
return RedirectToPage();
}
StatusMessage = "Your email is unchanged.";
return RedirectToPage();
}
public async Task<IActionResult> OnPostSendVerificationEmailAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var userId = await _userManager.GetUserIdAsync(user);
var email = await _userManager.GetEmailAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
StatusMessage = "Verification email sent. Please check your email.";
return RedirectToPage();
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[Required]
[EmailAddress]
[Display(Name = "New email")]
public string NewEmail { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,63 @@
@page
@model EnableAuthenticatorModel
@{
ViewData["Title"] = "Configure authenticator app";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}
<partial name="_StatusMessage" for="StatusMessage" />
<h4>@ViewData["Title"]</h4>
<div>
<p>To use an authenticator app go through the following steps:</p>
<ol class="list">
<li>
<p>
Download a two-factor authenticator app like Microsoft Authenticator for
<a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and
<a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or
Google Authenticator for
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=en">Android</a> and
<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>.
</p>
</li>
<li>
<p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
@*<div class="alert alert-info">Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable QR code generation</a>.</div>*@
<div id="qrCode"></div>
<div id="qrCodeData" data-url="@Html.Raw(@Model.AuthenticatorUri)"></div>
</li>
<li>
<p>
Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
with a unique code. Enter the code in the confirmation box below.
</p>
<div class="row">
<div class="col-md-6">
<form id="send-code" method="post">
<div class="form-group">
<label asp-for="Input.Code" class="control-label">Verification Code</label>
<input asp-for="Input.Code" class="form-control" autocomplete="off" />
<span asp-validation-for="Input.Code" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Verify</button>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</form>
</div>
</div>
</li>
</ol>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<script type="text/javascript" src="~/lib/qrcode.js"></script>
<script type="text/javascript">
new QRCode(document.getElementById("qrCode"),
{
text: "@Html.Raw(Model.AuthenticatorUri)",
width: 150,
height: 150
});
</script>
}
@@ -0,0 +1,183 @@
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Text;
using System.Text.Encodings.Web;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class EnableAuthenticatorModel : PageModel
{
#region Private Fields
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
private readonly ILogger<EnableAuthenticatorModel> _logger;
private readonly UrlEncoder _urlEncoder;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public EnableAuthenticatorModel(
UserManager<IdentityUser> userManager,
ILogger<EnableAuthenticatorModel> logger,
UrlEncoder urlEncoder)
{
_userManager = userManager;
_logger = logger;
_urlEncoder = urlEncoder;
}
#endregion Public Constructors
#region Public Properties
public string AuthenticatorUri { get; set; }
[BindProperty]
public InputModel Input { get; set; }
[TempData]
public string[] RecoveryCodes { get; set; }
public string SharedKey { get; set; }
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Private Methods
private string FormatKey(string unformattedKey)
{
var result = new StringBuilder();
int currentPosition = 0;
while (currentPosition + 4 < unformattedKey.Length)
{
result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" ");
currentPosition += 4;
}
if (currentPosition < unformattedKey.Length)
{
result.Append(unformattedKey.Substring(currentPosition));
}
return result.ToString().ToLowerInvariant();
}
private string GenerateQrCodeUri(string email, string unformattedKey)
{
return string.Format(
AuthenticatorUriFormat,
_urlEncoder.Encode("GWMS.UI"),
_urlEncoder.Encode(email),
unformattedKey);
}
private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user)
{
// Load the authenticator key & QR code URI to display on the form
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(unformattedKey))
{
await _userManager.ResetAuthenticatorKeyAsync(user);
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
}
SharedKey = FormatKey(unformattedKey);
var email = await _userManager.GetEmailAsync(user);
AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
}
#endregion Private Methods
#region Public Methods
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadSharedKeyAndQrCodeUriAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadSharedKeyAndQrCodeUriAsync(user);
return Page();
}
// Strip spaces and hypens
var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
if (!is2faTokenValid)
{
ModelState.AddModelError("Input.Code", "Verification code is invalid.");
await LoadSharedKeyAndQrCodeUriAsync(user);
return Page();
}
await _userManager.SetTwoFactorEnabledAsync(user, true);
var userId = await _userManager.GetUserIdAsync(user);
_logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
StatusMessage = "Your authenticator app has been verified.";
if (await _userManager.CountRecoveryCodesAsync(user) == 0)
{
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
RecoveryCodes = recoveryCodes.ToArray();
return RedirectToPage("./ShowRecoveryCodes");
}
else
{
return RedirectToPage("./TwoFactorAuthentication");
}
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Verification Code")]
public string Code { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,53 @@
@page
@model ExternalLoginsModel
@{
ViewData["Title"] = "Manage your external logins";
ViewData["ActivePage"] = ManageNavPages.ExternalLogins;
}
<partial name="_StatusMessage" for="StatusMessage" />
@if (Model.CurrentLogins?.Count > 0)
{
<h4>Registered Logins</h4>
<table class="table">
<tbody>
@foreach (var login in Model.CurrentLogins)
{
<tr>
<td id="@($"login-provider-{login.LoginProvider}")">@login.ProviderDisplayName</td>
<td>
@if (Model.ShowRemoveButton)
{
<form id="@($"remove-login-{login.LoginProvider}")" asp-page-handler="RemoveLogin" method="post">
<div>
<input asp-for="@login.LoginProvider" name="LoginProvider" type="hidden" />
<input asp-for="@login.ProviderKey" name="ProviderKey" type="hidden" />
<button type="submit" class="btn btn-primary" title="Remove this @login.ProviderDisplayName login from your account">Remove</button>
</div>
</form>
}
else
{
@: &nbsp;
}
</td>
</tr>
}
</tbody>
</table>
}
@if (Model.OtherLogins?.Count > 0)
{
<h4>Add another service to log in.</h4>
<hr />
<form id="link-login-form" asp-page-handler="LinkLogin" method="post" class="form-horizontal">
<div id="socialLoginList">
<p>
@foreach (var provider in Model.OtherLogins)
{
<button id="@($"link-login-button-{provider.Name}")" type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class ExternalLoginsModel : PageModel
{
#region Private Fields
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public ExternalLoginsModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
#endregion Public Constructors
#region Public Properties
public IList<UserLoginInfo> CurrentLogins { get; set; }
public IList<AuthenticationScheme> OtherLogins { get; set; }
public bool ShowRemoveButton { get; set; }
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID 'user.Id'.");
}
CurrentLogins = await _userManager.GetLoginsAsync(user);
OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
.Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
.ToList();
ShowRemoveButton = user.PasswordHash != null || CurrentLogins.Count > 1;
return Page();
}
public async Task<IActionResult> OnGetLinkLoginCallbackAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID 'user.Id'.");
}
var info = await _signInManager.GetExternalLoginInfoAsync(user.Id);
if (info == null)
{
throw new InvalidOperationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'.");
}
var result = await _userManager.AddLoginAsync(user, info);
if (!result.Succeeded)
{
StatusMessage = "The external login was not added. External logins can only be associated with one account.";
return RedirectToPage();
}
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
StatusMessage = "The external login was added.";
return RedirectToPage();
}
public async Task<IActionResult> OnPostLinkLoginAsync(string provider)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Page("./ExternalLogins", pageHandler: "LinkLoginCallback");
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return new ChallengeResult(provider, properties);
}
public async Task<IActionResult> OnPostRemoveLoginAsync(string loginProvider, string providerKey)
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID 'user.Id'.");
}
var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey);
if (!result.Succeeded)
{
StatusMessage = "The external login was not removed.";
return RedirectToPage();
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "The external login was removed.";
return RedirectToPage();
}
#endregion Public Methods
}
}
@@ -0,0 +1,27 @@
@page
@model GenerateRecoveryCodesModel
@{
ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}
<partial name="_StatusMessage" for="StatusMessage" />
<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>Put these codes in a safe place.</strong>
</p>
<p>
If you lose your device and don't have the recovery codes you will lose access to your account.
</p>
<p>
Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
</p>
</div>
<div>
<form method="post" class="form-group">
<button class="btn btn-danger" type="submit">Generate Recovery Codes</button>
</form>
</div>
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class GenerateRecoveryCodesModel : PageModel
{
#region Private Fields
private readonly ILogger<GenerateRecoveryCodesModel> _logger;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public GenerateRecoveryCodesModel(
UserManager<IdentityUser> userManager,
ILogger<GenerateRecoveryCodesModel> logger)
{
_userManager = userManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Properties
[TempData]
public string[] RecoveryCodes { get; set; }
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
if (!isTwoFactorEnabled)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' because they do not have 2FA enabled.");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
var userId = await _userManager.GetUserIdAsync(user);
if (!isTwoFactorEnabled)
{
throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' as they do not have 2FA enabled.");
}
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
RecoveryCodes = recoveryCodes.ToArray();
_logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
StatusMessage = "You have generated new recovery codes.";
return RedirectToPage("./ShowRecoveryCodes");
}
#endregion Public Methods
}
}
@@ -0,0 +1,30 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Profile";
ViewData["ActivePage"] = ManageNavPages.Index;
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" model="Model.StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="profile-form" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Username"></label>
<input asp-for="Username" class="form-control" disabled />
</div>
<div class="form-group">
<label asp-for="Input.PhoneNumber"></label>
<input asp-for="Input.PhoneNumber" class="form-control" />
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
<button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public partial class IndexModel : PageModel
{
#region Private Fields
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public IndexModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
#endregion Public Constructors
#region Public Properties
[BindProperty]
public InputModel Input { get; set; }
[TempData]
public string StatusMessage { get; set; }
public string Username { get; set; }
#endregion Public Properties
#region Private Methods
private async Task LoadAsync(IdentityUser user)
{
var userName = await _userManager.GetUserNameAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
Username = userName;
Input = new InputModel
{
PhoneNumber = phoneNumber
};
}
#endregion Private Methods
#region Public Methods
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
if (Input.PhoneNumber != phoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
StatusMessage = "Unexpected error when trying to set phone number.";
return RedirectToPage();
}
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public static class ManageNavPages
{
#region Public Properties
public static string ChangePassword => "ChangePassword";
public static string DeletePersonalData => "DeletePersonalData";
public static string DownloadPersonalData => "DownloadPersonalData";
public static string Email => "Email";
public static string ExternalLogins => "ExternalLogins";
public static string Index => "Index";
public static string PersonalData => "PersonalData";
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
#endregion Public Properties
#region Private Methods
private static string PageNavClass(ViewContext viewContext, string page)
{
var activePage = viewContext.ViewData["ActivePage"] as string
?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
}
#endregion Private Methods
#region Public Methods
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData);
public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData);
public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
#endregion Public Methods
}
}
@@ -0,0 +1,27 @@
@page
@model PersonalDataModel
@{
ViewData["Title"] = "Personal Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}
<h4>@ViewData["Title"]</h4>
<div class="row">
<div class="col-md-6">
<p>Your account contains personal data that you have given us. This page allows you to download or delete that data.</p>
<p>
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
</p>
<form id="download-data" asp-page="DownloadPersonalData" method="post" class="form-group">
<button class="btn btn-primary" type="submit">Download</button>
</form>
<p>
<a id="delete" asp-page="DeletePersonalData" class="btn btn-secondary">Delete</a>
</p>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,45 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class PersonalDataModel : PageModel
{
#region Private Fields
private readonly ILogger<PersonalDataModel> _logger;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public PersonalDataModel(
UserManager<IdentityUser> userManager,
ILogger<PersonalDataModel> logger)
{
_userManager = userManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Methods
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
return Page();
}
#endregion Public Methods
}
}
@@ -0,0 +1,24 @@
@page
@model ResetAuthenticatorModel
@{
ViewData["Title"] = "Reset authenticator key";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}
<partial name="_StatusMessage" for="StatusMessage" />
<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong>
</p>
<p>
This process disables 2FA until you verify your authenticator app.
If you do not complete your authenticator app configuration you may lose access to your account.
</p>
</div>
<div>
<form id="reset-authenticator-form" method="post" class="form-group">
<button id="reset-authenticator-button" class="btn btn-danger" type="submit">Reset authenticator key</button>
</form>
</div>
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class ResetAuthenticatorModel : PageModel
{
#region Private Fields
private readonly SignInManager<IdentityUser> _signInManager;
private ILogger<ResetAuthenticatorModel> _logger;
private UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public ResetAuthenticatorModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<ResetAuthenticatorModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Properties
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await _userManager.SetTwoFactorEnabledAsync(user, false);
await _userManager.ResetAuthenticatorKeyAsync(user);
_logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id);
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.";
return RedirectToPage("./EnableAuthenticator");
}
#endregion Public Methods
}
}
@@ -0,0 +1,35 @@
@page
@model SetPasswordModel
@{
ViewData["Title"] = "Set password";
ViewData["ActivePage"] = ManageNavPages.ChangePassword;
}
<h4>Set your password</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<p class="text-info">
You do not have a local username/password for this site. Add a local
account so you can log in without an external login.
</p>
<div class="row">
<div class="col-md-6">
<form id="set-password-form" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.NewPassword"></label>
<input asp-for="Input.NewPassword" class="form-control" />
<span asp-validation-for="Input.NewPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Set password</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class SetPasswordModel : PageModel
{
#region Private Fields
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public SetPasswordModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
#endregion Public Constructors
#region Public Properties
[BindProperty]
public InputModel Input { get; set; }
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (hasPassword)
{
return RedirectToPage("./ChangePassword");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);
if (!addPasswordResult.Succeeded)
{
foreach (var error in addPasswordResult.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your password has been set.";
return RedirectToPage();
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,25 @@
@page
@model ShowRecoveryCodesModel
@{
ViewData["Title"] = "Recovery codes";
ViewData["ActivePage"] = "TwoFactorAuthentication";
}
<partial name="_StatusMessage" for="StatusMessage" />
<h4>@ViewData["Title"]</h4>
<div class="alert alert-warning" role="alert">
<p>
<strong>Put these codes in a safe place.</strong>
</p>
<p>
If you lose your device and don't have the recovery codes you will lose access to your account.
</p>
</div>
<div class="row">
<div class="col-md-12">
@for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
{
<code class="recovery-code">@Model.RecoveryCodes[row]</code><text>&nbsp;</text><code class="recovery-code">@Model.RecoveryCodes[row + 1]</code><br />
}
</div>
</div>
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class ShowRecoveryCodesModel : PageModel
{
#region Public Properties
[TempData]
public string[] RecoveryCodes { get; set; }
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Public Methods
public IActionResult OnGet()
{
if (RecoveryCodes == null || RecoveryCodes.Length == 0)
{
return RedirectToPage("./TwoFactorAuthentication");
}
return Page();
}
#endregion Public Methods
}
}
@@ -0,0 +1,57 @@
@page
@model TwoFactorAuthenticationModel
@{
ViewData["Title"] = "Two-factor authentication (2FA)";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}
<partial name="_StatusMessage" for="StatusMessage" />
<h4>@ViewData["Title"]</h4>
@if (Model.Is2faEnabled)
{
if (Model.RecoveryCodesLeft == 0)
{
<div class="alert alert-danger">
<strong>You have no recovery codes left.</strong>
<p>You must <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a> before you can log in with a recovery code.</p>
</div>
}
else if (Model.RecoveryCodesLeft == 1)
{
<div class="alert alert-danger">
<strong>You have 1 recovery code left.</strong>
<p>You can <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
</div>
}
else if (Model.RecoveryCodesLeft <= 3)
{
<div class="alert alert-warning">
<strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong>
<p>You should <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
</div>
}
if (Model.IsMachineRemembered)
{
<form method="post" style="display: inline-block">
<button type="submit" class="btn btn-warning">Forget this browser</button>
</form>
}
<a asp-page="./Disable2fa" class="btn btn-danger">Disable 2FA</a>
<a asp-page="./GenerateRecoveryCodes" class="btn btn-warning">Reset recovery codes</a>
}
<h5>Authenticator app</h5>
@if (!Model.HasAuthenticator)
{
<a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Add authenticator app</a>
}
else
{
<a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Setup authenticator app</a>
<a id="reset-authenticator" asp-page="./ResetAuthenticator" class="btn btn-primary">Reset authenticator app</a>
}
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account.Manage
{
public class TwoFactorAuthenticationModel : PageModel
{
#region Private Fields
private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}";
private readonly ILogger<TwoFactorAuthenticationModel> _logger;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public TwoFactorAuthenticationModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<TwoFactorAuthenticationModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
#endregion Public Constructors
#region Public Properties
public bool HasAuthenticator { get; set; }
[BindProperty]
public bool Is2faEnabled { get; set; }
public bool IsMachineRemembered { get; set; }
public int RecoveryCodesLeft { get; set; }
[TempData]
public string StatusMessage { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null;
Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
IsMachineRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user);
RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user);
return Page();
}
public async Task<IActionResult> OnPost()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await _signInManager.ForgetTwoFactorClientAsync();
StatusMessage = "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code.";
return RedirectToPage();
}
#endregion Public Methods
}
}
@@ -0,0 +1,29 @@
@{
if (ViewData.TryGetValue("ParentLayout", out var parentLayout))
{
Layout = (string)parentLayout;
}
else
{
Layout = "/Areas/Identity/Pages/_Layout.cshtml";
}
}
<h2>Manage your account</h2>
<div>
<h4>Change your account settings</h4>
<hr />
<div class="row">
<div class="col-md-3">
<partial name="_ManageNav" />
</div>
<div class="col-md-9">
@RenderBody()
</div>
</div>
</div>
@section Scripts {
@RenderSection("Scripts", required: false)
}
@@ -0,0 +1,15 @@
@inject SignInManager<IdentityUser> SignInManager
@{
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<ul class="nav nav-pills flex-column">
<li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">Email</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li>
@if (hasExternalLogins)
{
<li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li>
}
<li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li>
</ul>
@@ -0,0 +1,10 @@
@model string
@if (!String.IsNullOrEmpty(Model))
{
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@Model
</div>
}
@@ -0,0 +1 @@
@using GWMS.UI.Areas.Identity.Pages.Account.Manage
@@ -0,0 +1,67 @@
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to register.</h4>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
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;
using Microsoft.Extensions.Logging;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
#region Private Fields
private readonly IEmailSender _emailSender;
private readonly ILogger<RegisterModel> _logger;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public RegisterModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
#endregion Public Constructors
#region Public Properties
public IList<AuthenticationScheme> ExternalLogins { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
#endregion Public Properties
#region Public Methods
public async Task OnGetAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
}
else
{
// aggiunta ruolo UNDEF (da sistemare poi)
// https://code-maze.com/using-roles-in-blazor-webassembly-hosted-applications/
await _userManager.AddToRoleAsync(user, "Undef");
// continuo come default
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,22 @@
@page
@model RegisterConfirmationModel
@{
ViewData["Title"] = "Register confirmation";
}
<h1>@ViewData["Title"]</h1>
@{
if (@Model.DisplayConfirmAccountLink)
{
<p>
This app does not currently have a real email sender registered, see <a href="https://aka.ms/aspaccountconf">these docs</a> for how to configure a real email sender.
Normally this would be emailed: <a id="confirm-link" href="@Model.EmailConfirmationUrl">Click here to confirm your account</a>
</p>
}
else
{
<p>
Please check your email to confirm your account.
</p>
}
}
@@ -0,0 +1,75 @@
using Microsoft.AspNetCore.Authorization;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
#region Private Fields
private readonly IEmailSender _sender;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
{
_userManager = userManager;
_sender = sender;
}
#endregion Public Constructors
#region Public Properties
public bool DisplayConfirmAccountLink { get; set; }
public string Email { get; set; }
public string EmailConfirmationUrl { get; set; }
#endregion Public Properties
#region Public Methods
public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
{
if (email == null)
{
return RedirectToPage("/Index");
}
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return NotFound($"Unable to load user with email '{email}'.");
}
Email = email;
// Once you add a real email sender, you should remove this code that lets you confirm the account
DisplayConfirmAccountLink = false;
if (DisplayConfirmAccountLink)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
EmailConfirmationUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
}
return Page();
}
#endregion Public Methods
}
}
@@ -0,0 +1,26 @@
@page
@model ResendEmailConfirmationModel
@{
ViewData["Title"] = "Resend email confirmation";
}
<h1>@ViewData["Title"]</h1>
<h4>Enter your email.</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Resend</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,97 @@
using System;
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 GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ResendEmailConfirmationModel : PageModel
{
#region Private Fields
private readonly IEmailSender _emailSender;
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public ResendEmailConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender emailSender)
{
_userManager = userManager;
_emailSender = emailSender;
}
#endregion Public Constructors
#region Public Properties
[BindProperty]
public InputModel Input { get; set; }
#endregion Public Properties
#region Public Methods
public void OnGet()
{
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
return Page();
}
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
Input.Email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
return Page();
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
[Required]
[EmailAddress]
public string Email { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,37 @@
@page
@model ResetPasswordModel
@{
ViewData["Title"] = "Reset password";
}
<h1>@ViewData["Title"]</h1>
<h4>Reset your password.</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input asp-for="Input.Code" type="hidden" />
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Reset</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ResetPasswordModel : PageModel
{
#region Private Fields
private readonly UserManager<IdentityUser> _userManager;
#endregion Private Fields
#region Public Constructors
public ResetPasswordModel(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
#endregion Public Constructors
#region Public Properties
[BindProperty]
public InputModel Input { get; set; }
#endregion Public Properties
#region Public Methods
public IActionResult OnGet(string code = null)
{
if (code == null)
{
return BadRequest("A code must be supplied for password reset.");
}
else
{
Input = new InputModel
{
Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code))
};
return Page();
}
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToPage("./ResetPasswordConfirmation");
}
var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password);
if (result.Succeeded)
{
return RedirectToPage("./ResetPasswordConfirmation");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
#endregion Public Methods
#region Public Classes
public class InputModel
{
#region Public Properties
public string Code { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
public string Password { get; set; }
#endregion Public Properties
}
#endregion Public Classes
}
}
@@ -0,0 +1,10 @@
@page
@model ResetPasswordConfirmationModel
@{
ViewData["Title"] = "Reset password confirmation";
}
<h1>@ViewData["Title"]</h1>
<p>
Your password has been reset. Please <a asp-page="./Login">click here to log in</a>.
</p>
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace GWMS.UI.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ResetPasswordConfirmationModel : PageModel
{
#region Public Methods
public void OnGet()
{
}
#endregion Public Methods
}
}
@@ -0,0 +1,10 @@
@model string
@if (!String.IsNullOrEmpty(Model))
{
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@Model
</div>
}
@@ -0,0 +1 @@
@using GWMS.UI.Areas.Identity.Pages.Account
+23
View File
@@ -0,0 +1,23 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>Development environment should not be enabled in deployed applications</strong>, 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 <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
</p>
@@ -0,0 +1,29 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace GWMS.UI.Areas.Identity.Pages
{
[AllowAnonymous]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
#region Public Properties
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
#endregion Public Properties
#region Public Methods
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
#endregion Public Methods
}
}
@@ -0,0 +1,4 @@
@using Microsoft.AspNetCore.Identity
@using GWMS.UI.Areas.Identity
@using GWMS.UI.Areas.Identity.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@@ -0,0 +1,4 @@

@{
Layout = "/Pages/Shared/_Layout.cshtml";
}
+4 -5
View File
@@ -7,14 +7,13 @@
@inject AuthenticationStateProvider AuthenticationStateProvider
<div class="row pt-3">
<div class="col-3">
<i class="fas fa-user-alt"></i> <b>@userName</b>
@*<LoginDisplay />*@
<div class="col-6 col-md-5 col-lg-4 col-xl-3 px-0 text-truncate">
<LoginDisplay></LoginDisplay>
</div>
<div class="col-6 text-center h4">
<div class="col-12 col-md-2 col-lg-4 col-xl-6 d-none d-lg-block text-center h4 px-1 text-truncate">
<span class="@PageIcon" aria-hidden="true"></span> @PageName
</div>
<div class="col-3 text-right">
<div class="col-6 col-md-5 col-lg-4 col-xl-3 text-right pl-0">
@if (ShowSearch)
{
<SearchMod></SearchMod>
+50 -8
View File
@@ -1,12 +1,54 @@
<AuthorizeView>
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider
<AuthorizeView>
<Authorized>
<a href="Identity/Account/Manage">Hello, @context.User.Identity.Name !!!</a>
<form method="post" action="Identity/Account/LogOut">
<button type="submit" class="nav-link btn btn-link">Log out</button>
</form>
<div class="input-group">
<div class="input-group-prepend">
<a title="LogOut" href="Identity/Account/LogOut" class="btn btn-sm btn-danger ml-3"><i class="fas fa-sign-out-alt"></i></a>
</div>
<a title="Manage" href="Identity/Account/Manage" class="btn btn-sm btn-outline-dark mx-0 px-1">
<i class="fas fa-user-alt"></i>&nbsp;@userName
</a>
</div>
</Authorized>
<NotAuthorized>
<a href="Identity/Account/Register">Register</a>
<a href="Identity/Account/Login">Log in</a>
<div class="input-group">
<div class="input-group-prepend">
<a title="LogIn" href="Identity/Account/LogIn" class="btn btn-sm btn-success ml-3"><i class="fas fa-sign-in-alt"></i></a>
</div>
<div class="form-control form-control-sm">
<i class="fas fa-user-alt"></i>&nbsp;@userName
</div>
<div class="input-group-append">
<a title="Register" href="Identity/Account/Register" class="btn btn-sm btn-primary ml-0"><i class="fas fa-edit"></i></a>
</div>
</div>
</NotAuthorized>
</AuthorizeView>
</AuthorizeView>
@code{
private string userName = "";
protected override async Task OnInitializedAsync()
{
await forceReload();
}
private async Task forceReload()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
userName = $"{user.Identity.Name}";
}
else
{
userName = "Non Autenticato";
}
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Version>1.0.2108.2412</Version>
<Version>1.0.2108.2516</Version>
<UserSecretsId>95c9f021-52d1-4390-a670-5810b7b777b0</UserSecretsId>
</PropertyGroup>
+2
View File
@@ -7,6 +7,8 @@
@inject GWMSDataService DataService
@inject MessageService AppMService
@attribute [AllowAnonymous]
<div class="jumbotron py-4">
<div class="row">
<div class="col-12 col-lg-4">
+67
View File
@@ -0,0 +1,67 @@
@using Microsoft.AspNetCore.Hosting
@using Microsoft.AspNetCore.Mvc.ViewEngines
@using GWMS.UI.Components
@inject IWebHostEnvironment Environment
@inject ICompositeViewEngine Engine
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes">
@*<meta name="viewport" content="width=device-width, initial-scale=1.0" />*@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>GWMS User Management</title>
<base href="~/" />
<link rel="stylesheet" href="~/Identity/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/Identity/css/site.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm bg-dark navbar-dark navbar-toggleable-sm border-bottom box-shadow py-0 my-0 fixed-top">
<div class="container">
<a class="navbar-brand" href="~/"><img src="./img/LogoBlu.svg" class="img-fluid" width="32" /> MAPO.GWMS</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
@{
var result = Engine.FindView(ViewContext, "_LoginPartial", isMainPage: false);
}
@if (result.Success)
{
await Html.RenderPartialAsync("_LoginPartial");
}
else
{
throw new InvalidOperationException("The default Identity UI layout requires a partial view '_LoginPartial' " +
"usually located at '/Pages/_LoginPartial' or at '/Views/Shared/_LoginPartial' to work. Based on your configuration " +
$"we have looked at it in the following locations: {System.Environment.NewLine}{string.Join(System.Environment.NewLine, result.SearchedLocations)}.");
}
</div>
</div>
</nav>
</header>
<div class="container mt-5">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="footer border-top text-muted fixed-bottom bottom-row bg-dark py-0 my-0">
<div class="container py-1">
&copy; 2021 - GWMS User Management- <a asp-area="" asp-page="Privacy">Privacy</a>
</div>
<CmpFooter></CmpFooter>
</footer>
<script src="~/Identity/lib/jquery/dist/jquery.min.js"></script>
<script src="~/Identity/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/Identity/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>
+27
View File
@@ -0,0 +1,27 @@
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
</li>
<li class="nav-item">
<form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/Index", new { area = "" })">
<button id="logout" type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" id="register" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" id="login" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
@@ -0,0 +1,18 @@
<environment include="Development">
<script src="~/Identity/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js"
asp-fallback-src="~/Identity/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.9/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
crossorigin="anonymous"
integrity="sha384-ifv0TYDWxBHzvAk2Z0n8R434FL1Rlv/Av18DXE43N/1rvHyOG4izKst0f2iSLdds">
</script>
</environment>
+173
View File
@@ -0,0 +1,173 @@
@page "/UserAdmin"
@using GWMS.UI.Pages
@if (ShowPopup)
{
<div class="modal" tabindex="-1" style="display:block" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Edit User</h3>
<!-- Button to close the popup -->
<button type="button" class="close" @onclick="ClosePopup">
<span aria-hidden="true">X</span>
</button>
</div>
<!-- Edit form for the current user -->
<div class="modal-body">
<!-- Only show Id if not a new user -->
@if (objUser.Id != "")
{
<p>@objUser.Id</p>
}
<!--<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-user"></i></span>
</div>-->
<!-- Only allow edit if a new user -->
<!--@if (objUser.Id != "")
{
<span class="form-control text-right">@objUser.UserName</span>
}
else
{
<input class="form-control text-right" type="text" placeholder="UserName" @bind="objUser.UserName" />
}
</div>-->
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="far fa-envelope"></i></span>
</div>
<input class="form-control" type="text" placeholder="Email" @bind="objUser.Email" />
<div class="input-group-append">
<div class="input-group-text">
<input class="form-control2" type="checkbox" title="Email Confirmed" @bind="objUser.EmailConfirmed" />
</div>
</div>
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-key"></i></span>
</div>
<input class="form-control" type="password" placeholder="Password" @bind="objUser.PasswordHash" />
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-user-tag"></i></span>
</div>
<select class="form-control" @bind="@CurrentUserRole">
@foreach (var option in RolesList)
{
<option value="@option">
@option
</option>
}
</select>
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-tag"></i></span>
</div>
<select class="form-control" @bind="@CurrentUserClaimType">
@foreach (var option in ClaimsList)
{
<option value="@option">
@option
</option>
}
</select>
<input class="form-control" title="Valore" type="text" @bind="CurrentUserClaimVal" />
</div>
<span style="color:red">@strError</span>
</div>
<div class="modal-footer">
<button class="btn btn-success" @onclick="SaveUser" title="Save"><i class="fas fa-check"></i> Save</button>
<button class="btn btn-primary" @onclick="ClosePopup"><i class="far fa-window-close"></i> Close</button>
</div>
</div>
</div>
</div>
}
<div class="card">
<div class="card-header">
<div class="row">
<div class="col-8 col-lg-9 col-xl-10 text-truncate">
<h3>User Administration</h3>
</div>
<div class="col-4 col-lg-3 col-xl-2 text-right">
<AuthorizeView>
<Authorized>
@if (@context.User.IsInRole("SuperAdmin"))
{
<button class="btn btn-success btn-block" @onclick="AddNewUser">Add User</button>
}
</Authorized>
</AuthorizeView>
</div>
</div>
</div>
<div class="card-body px-1">
<table class="table table-sm table-striped table-responsive-md">
<thead>
<tr>
<th></th>
<th>Id</th>
@*<th>User Name</th>*@
<th>User / Email</th>
<th>Ruolo</th>
<th>Permessi</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var user in UsersList)
{
<tr>
<td>
@if (!ShowPopup)
{
<button class="btn btn-sm btn-primary" @onclick="(() => EditUser(user.Identity))" title="Edit">
<i class="fas fa-pen"></i>
</button>
}
else
{
<button class="btn btn-sm btn-secondary disabled" title="Edit">
<i class="fas fa-pen"></i>
</button>
}
</td>
<td>@user.Identity.Id.Substring(0, 4)...</td>
@*<td>@user.UserName</td>*@
<td><span class="badge badge-pill @checkSem(user.Identity.EmailConfirmed)" title="Email validata"><span class="oi oi-check" aria-hidden="true"></span></span> @user.Identity.Email</td>
<td>
@ShowRoles(user.Roles)
</td>
<td>
@ShowClaims(user.Claims)
</td>
<td>
@if (!ShowPopup)
{
<button class="btn btn-sm btn-danger" @onclick="(() => DeleteUser(user.Identity))" title="Delete">
<i class="fas fa-trash"></i>
</button>
}
else
{
<button class="btn btn-sm btn-secondary disabled" title="Delete">
<i class="fas fa-trash"></i>
</button>
}
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="card-footer">
</div>
</div>
+428
View File
@@ -0,0 +1,428 @@
using GWMS.Data;
using GWMS.UI.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace GWMS.UI.Pages
{
[Authorize(Roles = "SuperAdmin, Admin")]
public partial class UserAdmin : ComponentBase, IDisposable
{
#region Private Fields
/// <summary>
/// Elenco CLAIMS da mostrare durante editing
/// </summary>
private List<string> ClaimsList = new List<string>() { STD_CLAIM, "PlantId", "SupplierId", "TransporterId" };
/// <summary>
/// User corrente
/// </summary>
private IdentityUser objUser = new IdentityUser();
/// <summary>
/// Elenco ROLES da mostrare in dropdown durante editing (1 solo? usare DB?!?)
/// </summary>
private List<string> RolesList = new List<string>() { "Undef", "ExtUser", "User", "Admin", "SuperAdmin" };
// To enable showing the Popup
private bool ShowPopup = false;
// To hold any possible errors
private string strError = "";
/// <summary>
/// Collezione utenti
/// </summary>
private List<UserData> UsersList = new List<UserData>();
#endregion Private Fields
#region Public Fields
public const string ADMIN_ROLE = "SuperAdmin";
public const string STD_CLAIM = "None";
public const string UNDEF_ROLE = "Undef";
#endregion Public Fields
#region Private Properties
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
private System.Security.Claims.Claim CurrentUserClaim
{
get => new System.Security.Claims.Claim(CurrentUserClaimType, CurrentUserClaimVal);
}
/// <summary>
/// Claim di default (Tipo)
/// </summary>
private string CurrentUserClaimType { get; set; } = "None";
/// <summary>
/// Claim di default (valore)
/// </summary>
private string CurrentUserClaimVal { get; set; } = "0";
/// <summary>
/// Ruolo di default (User!!!)
/// </summary>
private string CurrentUserRole { get; set; } = "User";
#endregion Private Properties
#region Protected Properties
[Inject]
protected RoleManager<IdentityRole> _RoleManager { get; set; }
[Inject]
protected UserManager<IdentityUser> _UserManager { get; set; }
[Inject]
protected MessageService AppMService { get; set; }
[Inject]
protected AuthenticationStateProvider AuthenticationStateProvider { get; set; }
[Inject]
protected IJSRuntime JSRuntime { get; set; }
#endregion Protected Properties
#region Private Methods
private void AddNewUser()
{
// Make new user
objUser = new IdentityUser();
objUser.PasswordHash = "*****";
// Set Id to blank so we know it is a new record
objUser.Id = "";
// Open the Popup
ShowPopup = true;
}
private async Task CheckSuperAdmin()
{
// se non ci fosse --> creo samuele come superadmin
string superUser = "samuele@steamware.net";
string superPwd = "viaDante16!";
var user = await _UserManager.FindByEmailAsync(superUser);
if (user == null)
{
// Insert new user
var NewUser =
new IdentityUser
{
UserName = superUser,
Email = superUser,
EmailConfirmed = true
};
var CreateResult = await _UserManager.CreateAsync(NewUser, superPwd);
if (CreateResult.Succeeded)
{
user = await _UserManager.FindByEmailAsync(superUser);
}
}
// verifico ruoli...
if (user != null)
{
// Gestione salvataggio ruoli... SE VARIATO...
var UserRoles = await _UserManager.GetRolesAsync(user);
if (UserRoles != null && UserRoles.Count > 0)
{
var oldRole = UserRoles.Where(x => x == ADMIN_ROLE).FirstOrDefault();
if (oldRole == null)
{
// aggiungo a ruolo admin
await _UserManager.AddToRoleAsync(user, ADMIN_ROLE);
}
}
}
}
private async Task ClosePopup()
{
// Close the Popup
ShowPopup = false;
// Refresh Users
await GetUsers();
}
private async Task DeleteUser(IdentityUser _IdentityUser)
{
// Close the Popup
ShowPopup = false;
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Sicuro di voler eliminare l'utente selezionato??"))
return;
// Get the user
var user = await _UserManager.FindByIdAsync(_IdentityUser.Id);
if (user != null)
{
// Delete the user
await _UserManager.DeleteAsync(user);
}
// Refresh Users
await GetUsers();
}
private void EditUser(IdentityUser _IdentityUser)
{
// selezione come current user
objUser = _IdentityUser;
var pUpd = Task.Run(async () =>
{
// Get the user
var user = await _UserManager.FindByIdAsync(objUser.Id);
if (user != null)
{
// salvo role
var UserRoles = await _UserManager.GetRolesAsync(user);
if (UserRoles != null)
{
CurrentUserRole = UserRoles.FirstOrDefault();
}
else
{
CurrentUserRole = UNDEF_ROLE;
}
// salvo claim
var UserClaims = await _UserManager.GetClaimsAsync(user);
if (UserClaims != null && UserClaims.Count > 0)
{
var CurrentUserClaim = UserClaims.FirstOrDefault();
CurrentUserClaimType = CurrentUserClaim.Type;
CurrentUserClaimVal = CurrentUserClaim.Value;
}
else
{
CurrentUserClaimType = STD_CLAIM;
CurrentUserClaimVal = "0";
}
}
});
pUpd.Wait();
// Open the Popup
ShowPopup = true;
}
private async Task SaveUser()
{
try
{
// Is this an existing user?
if (objUser.Id != "")
{
// Get the user
var user = await _UserManager.FindByIdAsync(objUser.Id);
// Update Email + check email
user.Email = objUser.Email;
user.EmailConfirmed = objUser.EmailConfirmed;
// Update the user
await _UserManager.UpdateAsync(user);
// Only update password if the current value
// is not the default value
if (objUser.PasswordHash != "*****")
{
var resetToken =
await _UserManager.GeneratePasswordResetTokenAsync(user);
var passworduser =
await _UserManager.ResetPasswordAsync(
user,
resetToken,
objUser.PasswordHash);
if (!passworduser.Succeeded)
{
if (passworduser.Errors.FirstOrDefault() != null)
{
strError =
passworduser
.Errors
.FirstOrDefault()
.Description;
}
else
{
strError = "Password error";
}
// Keep the popup opened
return;
}
}
// Gestione salvataggio ruoli... SE VARIATO...
var UserRoles = await _UserManager.GetRolesAsync(user);
if (UserRoles != null && UserRoles.Count > 0)
{
var oldRole = UserRoles.FirstOrDefault();
if (!CurrentUserRole.Equals(oldRole))
{
// recupero il ruolo attuale e lo rimuovo
await _UserManager.RemoveFromRoleAsync(user, oldRole);
}
// aggiungo il nuovo ruolo
await _UserManager.AddToRoleAsync(user, CurrentUserRole);
}
else
{
// aggiungo il nuovo ruolo
await _UserManager.AddToRoleAsync(user, CurrentUserRole);
}
// gestione salvataggio Claims
var UserClaims = await _UserManager.GetClaimsAsync(user);
if (UserClaims != null && UserClaims.Count > 0)
{
var oldClaim = UserClaims.FirstOrDefault();
if (!oldClaim.Equals(CurrentUserClaim))
{
// recupero il ruolo attuale e lo rimuovo
await _UserManager.RemoveClaimAsync(user, oldClaim);
}
// aggiungo il nuovo ruolo
await _UserManager.AddClaimAsync(user, CurrentUserClaim);
}
else
{
// aggiungo il nuovo ruolo
await _UserManager.AddClaimAsync(user, CurrentUserClaim);
}
}
else
{
// Insert new user
var NewUser =
new IdentityUser
{
UserName = objUser.Email,
Email = objUser.Email
};
var CreateResult = await _UserManager.CreateAsync(NewUser, objUser.PasswordHash);
if (!CreateResult.Succeeded)
{
if (CreateResult
.Errors
.FirstOrDefault() != null)
{
strError =
CreateResult
.Errors
.FirstOrDefault()
.Description;
}
else
{
strError = "Create error";
}
// Keep the popup opened
return;
}
else
{
// aggiungo a ruolo undef
await _UserManager.AddToRoleAsync(NewUser, UNDEF_ROLE);
// aggiungo claim undef...
await _UserManager.AddClaimAsync(NewUser, CurrentUserClaim);
}
}
// Close the Popup
ShowPopup = false;
// Refresh Users
await GetUsers();
}
catch (Exception ex)
{
strError = ex.GetBaseException().Message;
}
}
#endregion Private Methods
#region Protected Methods
protected override async Task OnInitializedAsync()
{
AppMService.ShowSearch = true;
AppMService.PageName = "Gestione Utenti";
AppMService.PageIcon = "fas fa-users pr-2";
// lettura dati
await GetUsers();
await CheckSuperAdmin();
}
#endregion Protected Methods
#region Public Methods
public string checkSem(bool valore)
{
return valore ? "badge-success" : "badge-danger";
}
public void Dispose()
{
}
/// <summary>
/// Recupera elenco utenti
/// </summary>
public async Task GetUsers()
{
// clear any error messages
strError = "";
// Collection to hold users
UsersList = new List<UserData>();
// get users from _UserManager
var user = _UserManager.Users.Select(x => new IdentityUser
{
Id = x.Id,
UserName = x.UserName,
Email = x.Email,
PhoneNumber = x.PhoneNumber,
PasswordHash = "*****",
EmailConfirmed = x.EmailConfirmed
}).ToList();
foreach (var item in user)
{
var UserRoles = await _UserManager.GetRolesAsync(item);
var UserClaims = await _UserManager.GetClaimsAsync(item);
var newItem = new UserData()
{
Identity = item,
Roles = UserRoles.ToList(),
Claims = UserClaims.ToList()
};
UsersList.Add(newItem);
}
}
public string ShowClaims(List<System.Security.Claims.Claim> ClaimList)
{
string answ = string.Join(",", ClaimList);
return answ;
}
public string ShowRoles(List<string> RoleList)
{
string answ = string.Join(",", RoleList);
return answ;
}
#endregion Public Methods
}
}
+3 -1
View File
@@ -21,8 +21,9 @@
<!-- inside of head section -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.12.0/css/all.css">
<!--<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.12.0/css/all.css"> -->
<link rel="stylesheet" href="font-awesome/css/fontawesome.css" />
<link href="_content/Blazorise/blazorise.css" rel="stylesheet" />
<link href="_content/Blazorise.Bootstrap/blazorise.bootstrap.css" rel="stylesheet" />
@@ -57,6 +58,7 @@
<script src="_content/BlazorBarcodeScanner.ZXing.JS/zxingjs.index.min.js"></script>
<script src="_content/BlazorBarcodeScanner.ZXing.JS/BlazorBarcodeScanner.js"></script>
<script src="font-awesome/js/all.js"></script>
<script src="_framework/blazor.server.js"></script>
</body>
</html>
+1 -1
View File
@@ -12,7 +12,7 @@
<CascadingValue Name="ShowSearch" Value=@ShowSearch>
<div class="main">
<div class="top-row">
<div class="top-row px-0">
<CmpTop></CmpTop>
</div>
<div class="content pt-1 pt-lg-2 px-2 mb-5">
+6 -4
View File
@@ -50,7 +50,7 @@
overflow: hidden;
text-overflow: ellipsis;
}
/*
@media (max-width: 640.98px) {
.top-row:not(.auth) {
display: none;
@@ -62,8 +62,8 @@
.top-row a, .top-row .btn-link {
margin-left: 0;
}
}
}*/
@media (min-width: 641px) {
.page {
@@ -97,7 +97,9 @@
}
.main > div {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
/*padding-left: 2rem !important;
padding-right: 1.5rem !important;*/
}
}
+45 -15
View File
@@ -1,17 +1,21 @@
<div class="top-row pl-3 navbar navbar-dark">
@using GWMS.UI.Components
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider
<div class="top-row pl-3 navbar navbar-dark">
<div class="text-nowrap">
@if (showText)
{
<a class="navbar-brand" @onclick="() => ToggleCompress()" title="Comprimi">
<img src="./img/LogoBlu.svg" class="img-fluid" width="32" />
@*<i class="fas fa-chevron-left"></i>*@
@*<i class="fas fa-2x fa-chevron-left"></i>*@
</a>
}
else
{
<a class="navbar-brand" @onclick="() => ToggleCompress()" title="Comprimi">
<img src="./img/LogoBlu.svg" class="img-fluid" width="32" />
@*<i class="fas fa-chevron-right"></i>*@
@*<i class="fas fa-2x fa-chevron-right"></i>*@
</a>
}
<a class="navbar-brand" href="">
@@ -27,62 +31,68 @@
<ul class="nav flex-column">
<li class="nav-item px-3" title="Home">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="fas fa-home pr-2" aria-hidden="true"></span>
<span class="fas fa-2x fa-home pr-2" aria-hidden="true"></span>
<span class="@hideText">Home</span>
</NavLink>
</li>
<li class="nav-item px-3" title="Stato Impianti">
<NavLink class="nav-link" href="PlantStatus">
<i class="fas fa-tachometer-alt pr-2"></i>
<i class="fas fa-2x fa-tachometer-alt pr-2"></i>
<span class="@hideText">Stato Impianti</span>
</NavLink>
</li>
<li class="nav-item px-3" title="Storico Ordini">
<NavLink class="nav-link" href="Orders">
<i class="fas fa-file-invoice pr-2" aria-hidden="true"></i>
<i class="fas fa-2x fa-file-invoice pr-2" aria-hidden="true"></i>
<span class="@hideText">Storico Ordini</span>
</NavLink>
</li>
<li class="nav-item px-3" title="Scheda Fornitore">
<NavLink class="nav-link" href="Suppliers">
<i class="fas fa-industry pr-2" aria-hidden="true"></i>
<i class="fas fa-2x fa-industry pr-2" aria-hidden="true"></i>
<span class="@hideText">Scheda Fornitore</span>
</NavLink>
</li>
<li class="nav-item px-3" title="Scheda Trasportatore">
<NavLink class="nav-link" href="Transporters">
<i class="fas fa-truck-moving pr-2" aria-hidden="true"></i>
<i class="fas fa-2x fa-truck-moving pr-2" aria-hidden="true"></i>
<span class="@hideText">Scheda Trasportatore</span>
</NavLink>
</li>
<li class="nav-item px-3" title="Scheda Stazione">
<NavLink class="nav-link" href="GasStation">
<i class="fas fa-gas-pump pr-2" aria-hidden="true"></i>
<i class="fas fa-2x fa-gas-pump pr-2" aria-hidden="true"></i>
<span class="@hideText">Scheda Stazione</span>
</NavLink>
</li>
<li class="nav-item px-3" title="Planner Consegne">
<NavLink class="nav-link" href="WeekPlan">
<i class="fas fa-calendar pr-2" aria-hidden="true"></i>
<i class="fas fa-2x fa-calendar pr-2" aria-hidden="true"></i>
<span class="@hideText">Planner Consegne</span>
</NavLink>
</li>
<li class="nav-item px-3" title="Utenti">
<NavLink class="nav-link" href="UserManager">
<i class="fas fa-users pr-2" aria-hidden="true"></i>
<span class="@hideText">Utenti</span>
<NavLink class="nav-link" href="UserAdmin">
<i class="fas fa-2x fa-users pr-2" aria-hidden="true"></i>
<span class="@hideText">Admin Utenti</span>
</NavLink>
</li>
@*<li class="nav-item px-3" title="Utenti">
<NavLink class="nav-link" href="UserManager">
<i class="fas fa-2x fa-users pr-2" aria-hidden="true"></i>
<span class="@hideText">Utenti</span>
</NavLink>
</li>*@
@*<li class="nav-item px-3" title="Job Scheduler">
<NavLink class="nav-link" href="Scheduler">
<i class="fas fa-calendar-alt pr-2" aria-hidden="true"></i>
<i class="fas fa-2x fa-calendar-alt pr-2" aria-hidden="true"></i>
<span class="@hideText">Job Scheduler</span>
</NavLink>
</li>
<li class="nav-item px-3" title="Setup Parametri">
<NavLink class="nav-link" href="Parameters">
<i class="fas fa-wrench pr-2" aria-hidden="true"></i>
<i class="fas fa-2x fa-wrench pr-2" aria-hidden="true"></i>
<span class="@hideText">Setup Parametri</span>
</NavLink>
</li>*@
@@ -90,10 +100,30 @@
</div>
@code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationStateTask { get; set; }
private bool collapseNavMenu = true;
private string userName = "";
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
userName = $"{user.Identity.Name}";
}
else
{
userName = "Non Autenticato";
}
}
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
+27
View File
@@ -6,6 +6,7 @@ using GWMS.Data;
using GWMS.UI.Areas.Identity;
using GWMS.UI.Data;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
@@ -175,14 +176,40 @@ namespace GWMS.UI
options.Sender_Name = Configuration["ExternalProviders:MailKit:SMTP:SenderName"];
});
// cookie applicazione da 14 gg (defaul) a 30
services.ConfigureApplicationCookie(o =>
{
o.ExpireTimeSpan = TimeSpan.FromDays(30);
o.SlidingExpiration = true;
});
// token di sicurezza dati a 3h
services.Configure<DataProtectionTokenProviderOptions>(o =>
o.TokenLifespan = TimeSpan.FromHours(3));
// setup MySql
//string connString = Configuration.GetConnectionString("AdminConnection");
var serverVersion = DbConfig.MysqlServerVersion(connStringDB);
services.AddDbContext<UserIdentityDbContext>(options =>
options.UseMySql(connStringDB, serverVersion));
// identity management
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<UserIdentityDbContext>();
// rif auth
// https://stackoverflow.com/questions/60687879/require-authorization-on-all-blazor-pages/60688109#60688109
// https://www.c-sharpcorner.com/article/understand-basic-of-authorization-in-blazor-server-app/#:~:text=Authentication%20is%20a%20process%20of%20validating%20a%20user,UI%20option%20can%20be%20accessible%20by%20the%20user.
// non funziona --> messo attributo in _Imports.razor e esclusione in Index page
#if false
// richiesta esplicita autorizzazione
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
#endif
services.AddBlazorise(options =>
{
+3 -1
View File
@@ -10,4 +10,6 @@
@using GWMS.UI
@using GWMS.UI.Shared
@using Blazorise
@using Blazorise.Charts
@using Blazorise.Charts
@attribute [Authorize]
+3
View File
@@ -57,6 +57,9 @@ a,
right: 0.75rem;
top: 0.5rem;
}
.footer {
line-height: 1.8em;
}
/*------------------------------------------------------------------
[ Shortcuts / .shortcuts ]
*/
+4
View File
@@ -55,6 +55,10 @@ a, .btn-link {
top: 0.5rem;
}
.footer {
line-height: 1.8em;
}
/*------------------------------------------------------------------
[ Shortcuts / .shortcuts ]
*/
+1 -1
View File
@@ -1 +1 @@
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');@import url('fonts.min.css');h1,h2,h3,h4,h5,h6,b,display-1,display-2,display-3,display-4{font-family:'Lato',sans-serif;}html,body,.textCondensed{font-family:'Roboto Condensed',sans-serif;}a,.btn-link{color:#0366d6;}.btn-primary{color:#fff;background-color:#1b6ec2;border-color:#1861ac;}.content{padding-top:1.1rem;}.valid.modified:not([type=checkbox]){outline:1px solid #26b050;}.invalid{outline:1px solid #f00;}.validation-message{color:#f00;}#blazor-error-ui{background:#ffffe0;bottom:0;box-shadow:0 -1px 2px rgba(0,0,0,.2);display:none;left:0;padding:.6rem 1.25rem .7rem 1.25rem;position:fixed;width:100%;z-index:1000;}#blazor-error-ui .dismiss{cursor:pointer;position:absolute;right:.75rem;top:.5rem;}.shortcuts{text-align:center;}.shortcuts .shortcut-icon{font-size:2rem;}.shortcuts .shortcut{min-width:9rem;min-height:5rem;display:inline-block;padding:.66666667rem 0;margin:0 2px 1em;vertical-align:top;text-decoration:none;background:#f3f3f3;background-image:-webkit-gradient(linear,left 0%,left 100%,from(#fff),to(#eee));background-image:-webkit-linear-gradient(top,#fff,0%,#eee,100%);background-image:-moz-linear-gradient(top,#fff 0%,#eee 100%);background-image:linear-gradient(to bottom,#fff 0%,#eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffeeeeee',GradientType=0);border:1px solid #ddd;box-sizing:border-box;border-radius:.5rem;}.shortcuts .shortcut-sm{min-width:4.5rem;min-height:3rem;display:inline-block;padding:.25rem 0;margin:0 2px 1em;vertical-align:top;text-decoration:none;background:#f3f3f3;background-image:-webkit-gradient(linear,left 0%,left 100%,from(#fff),to(#eee));background-image:-webkit-linear-gradient(top,#fff,0%,#eee,100%);background-image:-moz-linear-gradient(top,#fff 0%,#eee 100%);background-image:linear-gradient(to bottom,#fff 0%,#eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffeeeeee',GradientType=0);border:1px solid #ddd;box-sizing:border-box;border-radius:.5rem;}.shortcuts .shortcut .shortcut-icon{width:100%;margin-top:0;margin-bottom:0;font-size:2rem;color:#333;}.shortcuts .shortcut-sm .shortcut-icon{width:100%;margin-top:0;margin-bottom:0;font-size:2rem;color:#333;}.shortcuts .shortcut:hover{background:#e8e8e8;background-image:-webkit-gradient(linear,left 0%,left 100%,from(#fafafa),to(#e1e1e1));background-image:-webkit-linear-gradient(top,#fafafa,0%,#e1e1e1,100%);background-image:-moz-linear-gradient(top,#fafafa 0%,#e1e1e1 100%);background-image:linear-gradient(to bottom,#fafafa 0%,#e1e1e1 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa',endColorstr='#ffe1e1e1',GradientType=0);}.shortcuts .shortcut-sm:hover{background:#e8e8e8;background-image:-webkit-gradient(linear,left 0%,left 100%,from(#fafafa),to(#e1e1e1));background-image:-webkit-linear-gradient(top,#fafafa,0%,#e1e1e1,100%);background-image:-moz-linear-gradient(top,#fafafa 0%,#e1e1e1 100%);background-image:linear-gradient(to bottom,#fafafa 0%,#e1e1e1 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa',endColorstr='#ffe1e1e1',GradientType=0);}.shortcuts .shortcut:active{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);}.shortcuts .shortcut-sm:active{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);}.shortcuts .shortcut:hover .shortcut-icon{color:#c93;}.shortcuts .shortcut-sm:hover .shortcut-icon{color:#666;}.shortcuts .shortcut-label{display:block;margin-top:.75em;font-weight:400;color:#666;}@media(max-width:640px){.shortcuts .shortcut{min-width:8rem;min-height:4rem;}body{font-size:.8em;}}
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');@import url('fonts.min.css');h1,h2,h3,h4,h5,h6,b,display-1,display-2,display-3,display-4{font-family:'Lato',sans-serif;}html,body,.textCondensed{font-family:'Roboto Condensed',sans-serif;}a,.btn-link{color:#0366d6;}.btn-primary{color:#fff;background-color:#1b6ec2;border-color:#1861ac;}.content{padding-top:1.1rem;}.valid.modified:not([type=checkbox]){outline:1px solid #26b050;}.invalid{outline:1px solid #f00;}.validation-message{color:#f00;}#blazor-error-ui{background:#ffffe0;bottom:0;box-shadow:0 -1px 2px rgba(0,0,0,.2);display:none;left:0;padding:.6rem 1.25rem .7rem 1.25rem;position:fixed;width:100%;z-index:1000;}#blazor-error-ui .dismiss{cursor:pointer;position:absolute;right:.75rem;top:.5rem;}.footer{line-height:1.8em;}.shortcuts{text-align:center;}.shortcuts .shortcut-icon{font-size:2rem;}.shortcuts .shortcut{min-width:9rem;min-height:5rem;display:inline-block;padding:.66666667rem 0;margin:0 2px 1em;vertical-align:top;text-decoration:none;background:#f3f3f3;background-image:-webkit-gradient(linear,left 0%,left 100%,from(#fff),to(#eee));background-image:-webkit-linear-gradient(top,#fff,0%,#eee,100%);background-image:-moz-linear-gradient(top,#fff 0%,#eee 100%);background-image:linear-gradient(to bottom,#fff 0%,#eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffeeeeee',GradientType=0);border:1px solid #ddd;box-sizing:border-box;border-radius:.5rem;}.shortcuts .shortcut-sm{min-width:4.5rem;min-height:3rem;display:inline-block;padding:.25rem 0;margin:0 2px 1em;vertical-align:top;text-decoration:none;background:#f3f3f3;background-image:-webkit-gradient(linear,left 0%,left 100%,from(#fff),to(#eee));background-image:-webkit-linear-gradient(top,#fff,0%,#eee,100%);background-image:-moz-linear-gradient(top,#fff 0%,#eee 100%);background-image:linear-gradient(to bottom,#fff 0%,#eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffeeeeee',GradientType=0);border:1px solid #ddd;box-sizing:border-box;border-radius:.5rem;}.shortcuts .shortcut .shortcut-icon{width:100%;margin-top:0;margin-bottom:0;font-size:2rem;color:#333;}.shortcuts .shortcut-sm .shortcut-icon{width:100%;margin-top:0;margin-bottom:0;font-size:2rem;color:#333;}.shortcuts .shortcut:hover{background:#e8e8e8;background-image:-webkit-gradient(linear,left 0%,left 100%,from(#fafafa),to(#e1e1e1));background-image:-webkit-linear-gradient(top,#fafafa,0%,#e1e1e1,100%);background-image:-moz-linear-gradient(top,#fafafa 0%,#e1e1e1 100%);background-image:linear-gradient(to bottom,#fafafa 0%,#e1e1e1 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa',endColorstr='#ffe1e1e1',GradientType=0);}.shortcuts .shortcut-sm:hover{background:#e8e8e8;background-image:-webkit-gradient(linear,left 0%,left 100%,from(#fafafa),to(#e1e1e1));background-image:-webkit-linear-gradient(top,#fafafa,0%,#e1e1e1,100%);background-image:-moz-linear-gradient(top,#fafafa 0%,#e1e1e1 100%);background-image:linear-gradient(to bottom,#fafafa 0%,#e1e1e1 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa',endColorstr='#ffe1e1e1',GradientType=0);}.shortcuts .shortcut:active{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);}.shortcuts .shortcut-sm:active{box-shadow:inset 0 3px 5px rgba(0,0,0,.125);}.shortcuts .shortcut:hover .shortcut-icon{color:#c93;}.shortcuts .shortcut-sm:hover .shortcut-icon{color:#666;}.shortcuts .shortcut-label{display:block;margin-top:.75em;font-weight:400;color:#666;}@media(max-width:640px){.shortcuts .shortcut{min-width:8rem;min-height:4rem;}body{font-size:.8em;}}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
/*!
* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face {
font-family: 'Font Awesome 5 Brands';
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/fa-brands-400.eot");
src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); }
.fab {
font-family: 'Font Awesome 5 Brands';
font-weight: 400; }
+5
View File
@@ -0,0 +1,5 @@
/*!
* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands";font-weight:400}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More