Ported progress from SPA and began scaffolding Identity UI.

Laid some groundwork in backend.

Began customizing Identity UI.
This commit is contained in:
2021-07-21 01:58:49 -05:00
parent 57f67391f1
commit b43d7bab84
44 changed files with 1225 additions and 620 deletions

View File

@@ -0,0 +1,7 @@
@page
@model ConfirmEmailModel
@{
ViewData["Title"] = "Confirm email";
}
<h1>@ViewData["Title"]</h1>

View File

@@ -0,0 +1,48 @@
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;
using Props.Models;
using Props.Models.User;
namespace Props.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ConfirmEmailModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
public ConfirmEmailModel(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[TempData]
public string StatusMessage { get; set; }
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();
}
}
}

View File

@@ -5,65 +5,54 @@
ViewData["Title"] = "Log in";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<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 class="flex-grow-1 d-flex flex-column justify-content-center">
<div class="jumbotron border-top border-bottom">
<h1 class="mx-auto mt-3 mb-4 text-center">@ViewData["Title"]</h1>
<div class="my-3 row justify-content-md-center">
<div class="col-md-4">
<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="mb-3">
<label asp-for="Input.Email" class="form-label"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Input.Password" class="form-label"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="mb-3">
<div class="form-check">
<input asp-for="Input.RememberMe" class="form-check-input" />
<label asp-for="Input.RememberMe" class="form-check-label">
@Html.DisplayNameFor(m => m.Input.RememberMe)
</label>
</div>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Log in</button>
</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 class="my-1">
<a class="link-secondary" id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a>
</div>
<div class="my-1">
<a class="link-secondary" asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
</div>
<div class="my-1">
<a class="link-secondary" id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a>
</div>
</div>
}
else
{
</form>
</div>
@if ((Model.ExternalLogins?.Count ?? 0) != 0)
{
<div class="col-md-6 md-offset-2">
<h4>Use another service to log in.</h4>
<hr />
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@@ -74,12 +63,13 @@
</p>
</div>
</form>
}
</div>
}
</section>
</div>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -4,14 +4,15 @@ 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.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Props.Models;
using Props.Models.User;
namespace Props.Areas.Identity.Pages.Account
{
@@ -22,7 +23,7 @@ namespace Props.Areas.Identity.Pages.Account
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<LoginModel> _logger;
public LoginModel(SignInManager<ApplicationUser> signInManager,
public LoginModel(SignInManager<ApplicationUser> signInManager,
ILogger<LoginModel> logger,
UserManager<ApplicationUser> userManager)
{
@@ -77,7 +78,7 @@ namespace Props.Areas.Identity.Pages.Account
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout

View File

@@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Props.Models;
using Props.Models.User;
namespace Props.Areas.Identity.Pages.Account
{

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Props.Areas.Identity.Pages.Account.Manage
{
public static class ManageNavPages
{
public static string Index => "Index";
public static string Email => "Email";
public static string ChangePassword => "ChangePassword";
public static string DownloadPersonalData => "DownloadPersonalData";
public static string DeletePersonalData => "DeletePersonalData";
public static string ExternalLogins => "ExternalLogins";
public static string PersonalData => "PersonalData";
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData);
public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData);
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
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;
}
}
}

View File

@@ -0,0 +1,32 @@
@{
if (ViewData.TryGetValue("ParentLayout", out var parentLayout))
{
Layout = (string)parentLayout;
}
else
{
Layout = "/Areas/Identity/Pages/_Layout.cshtml";
}
}
<div class="container">
<h1>Manage your account</h1>
<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>
</div>
@section Scripts {
@RenderSection("Scripts", required: false)
}

View File

@@ -0,0 +1,15 @@
@inject SignInManager<ApplicationUser> 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>

View File

@@ -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>
}

View File

@@ -0,0 +1 @@
@using Props.Areas.Identity.Pages.Account.Manage

View File

@@ -4,48 +4,38 @@
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 class="flex-grow-1 d-flex flex-column justify-content-center">
<div class="jumbotron border-top border-bottom">
<h1 class="mx-auto mt-3 mb-4 text-center">@ViewData["Title"]</h1>
<div class="my-3 row justify-content-md-center">
<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="mb-3">
<label asp-for="Input.Email" class="form-label"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
}
else
{
<div class="mb-3">
<label asp-for="Input.Password" class="form-label"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Input.ConfirmPassword" class="form-label"></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>
@if ((Model.ExternalLogins?.Count ?? 0) != 0)
{
<div class="col-md-6 md-offset-2">
<h4>Use another service to register.</h4>
<hr />
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@@ -56,12 +46,13 @@
</p>
</div>
</form>
}
</div>
}
</section>
</div>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<partial name="_ValidationScriptsPartial" />
}

View File

@@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using Props.Models;
using Props.Models.User;
namespace Props.Areas.Identity.Pages.Account
{

View File

@@ -8,15 +8,16 @@
@{
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>
<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>
<p>
Please check your email to confirm your account.
</p>
}
}
@* TODO: https://aka.ms/aspaccountconf *@

View File

@@ -1,12 +1,13 @@
using Microsoft.AspNetCore.Authorization;
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;
using Props.Models;
using Props.Models.User;
namespace Props.Areas.Identity.Pages.Account
{

View File

@@ -2,4 +2,4 @@
@using Props.Areas.Identity
@using Props.Areas.Identity.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using Props.Models
@using Props.Models.User