Created first time setup to create initial admin account

dashboard
Pat Hartl 2023-01-04 01:46:31 -06:00
parent a0d7134af3
commit 5bcf8d3e3c
6 changed files with 268 additions and 5 deletions

View File

@ -0,0 +1,52 @@
@page
@model FirstTimeSetupModel
@{
Layout = "/Views/Shared/_LayoutBasic.cshtml";
}
@{
ViewData["Title"] = "First Time Setup";
}
<div class="page page-center">
<form asp-route-returnUrl="@Model.ReturnUrl" class="container-tight py-4">
<div class="text-center mb-4">
<h2>LANCommander</h2>
</div>
<div class="card card-md">
<div class="card-body text-center py-4 p-sm-5">
<h1>Welcome to LANCommander!</h1>
<p class="text-muted">LANCommander is your one stop shop for distributing games on your LAN. Start your adventure with LANCommander and take control of your local multiplayer gaming!</p>
</div>
<div class="hr-text hr-text-center hr-text-spaceless">registration</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Register your admin account</label>
<input asp-for="Input.UserName" type="text" class="form-control ps-1" autocomplete="off" placeholder="Username" />
<div class="form-hint">For first-time setup, an admin user is required. This user will be able to manage all aspects of the application.</div>
</div>
<div class="mb-3">
<label asp-for="Input.Password" class="form-label"></label>
<input asp-for="Input.Password" type="password" class="form-control ps-1" autocomplete="new-password" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Input.ConfirmPassword" class="form-label"></label>
<input asp-for="Input.ConfirmPassword" type="password" class="form-control ps-1" autocomplete="new-password" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
</div>
</div>
<div class="row align-items-center mt-3">
<div class="col">
<div class="btn-list justify-content-end">
<button type="submit" class="btn btn-primary">Continue</button>
</div>
</div>
</div>
</form>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@ -0,0 +1,184 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using LANCommander.Data.Models;
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 LANCommander.Areas.Identity.Pages.Account
{
public class FirstTimeSetupModel : PageModel
{
private readonly SignInManager<User> _signInManager;
private readonly UserManager<User> _userManager;
private readonly RoleManager<Role> _roleManager;
private readonly IUserStore<User> _userStore;
private readonly IUserEmailStore<User> _emailStore;
private readonly ILogger<FirstTimeSetupModel> _logger;
private readonly IEmailSender _emailSender;
public FirstTimeSetupModel(
UserManager<User> userManager,
IUserStore<User> userStore,
SignInManager<User> signInManager,
RoleManager<Role> roleManager,
ILogger<FirstTimeSetupModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_userStore = userStore;
_signInManager = signInManager;
_roleManager = roleManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string ReturnUrl { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public IList<AuthenticationScheme> ExternalLogins { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[Display(Name = "Username")]
public string UserName { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[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; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task<IActionResult> OnGetAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
var administratorRoleExists = await _roleManager.RoleExistsAsync("Administor");
if (!administratorRoleExists)
await _roleManager.CreateAsync(new Role()
{
Name = "Administrator"
});
var administrators = await _userManager.GetUsersInRoleAsync("Administrator");
if (administrators.Count > 0)
return RedirectToPage("./Login");
return Page();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
var administrators = await _userManager.GetUsersInRoleAsync("Administrator");
if (administrators.Count > 0)
return RedirectToPage("./Login");
if (ModelState.IsValid)
{
var user = CreateUser();
await _userStore.SetUserNameAsync(user, Input.UserName, CancellationToken.None);
var result = await _userManager.CreateAsync(user, Input.Password);
await _userManager.AddToRoleAsync(user, "Administrator");
if (result.Succeeded)
{
_logger.LogInformation("Administrator created a new account with password.");
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, returnUrl = returnUrl },
protocol: Request.Scheme);
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();
}
private User CreateUser()
{
try
{
return Activator.CreateInstance<User>();
}
catch
{
throw new InvalidOperationException($"Can't create an instance of '{nameof(User)}'. " +
$"Ensure that '{nameof(User)}' is not an abstract class and has a parameterless constructor, or alternatively " +
$"override the register page in /Areas/Identity/Pages/Account/FirstTimeSetupModel.cshtml");
}
}
}
}

View File

@ -21,11 +21,17 @@ namespace LANCommander.Areas.Identity.Pages.Account
public class LoginModel : PageModel
{
private readonly SignInManager<User> _signInManager;
private readonly UserManager<User> _userManager;
private readonly ILogger<LoginModel> _logger;
public LoginModel(SignInManager<User> signInManager, ILogger<LoginModel> logger)
public LoginModel(
SignInManager<User> signInManager,
UserManager<User> userManager,
ILogger<LoginModel> logger
)
{
_signInManager = signInManager;
_userManager = userManager;
_logger = logger;
}
@ -86,7 +92,7 @@ namespace LANCommander.Areas.Identity.Pages.Account
public bool RememberMe { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
public async Task<IActionResult> OnGetAsync(string returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
@ -101,6 +107,15 @@ namespace LANCommander.Areas.Identity.Pages.Account
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
ReturnUrl = returnUrl;
var administrators = await _userManager.GetUsersInRoleAsync("Administrator");
if (administrators.Count == 0)
{
return RedirectToPage("./FirstTimeSetup");
}
return Page();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)

View File

@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
namespace LANCommander.Data
{
public class DatabaseContext : IdentityDbContext<User, IdentityRole<Guid>, Guid>
public class DatabaseContext : IdentityDbContext<User, Role, Guid>
{
public DatabaseContext(DbContextOptions<DatabaseContext> options)
: base(options)

10
Data/Models/Role.cs Normal file
View File

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations.Schema;
namespace LANCommander.Data.Models
{
[Table("Roles")]
public class Role : IdentityRole<Guid>
{
}
}

View File

@ -21,7 +21,9 @@ builder.Services.AddDefaultIdentity<User>((IdentityOptions options) => {
options.SignIn.RequireConfirmedAccount = false;
options.Password.RequireNonAlphanumeric = false;
options.SignIn.RequireConfirmedEmail = false;
}).AddEntityFrameworkStores<LANCommander.Data.DatabaseContext>();
})
.AddRoles<Role>()
.AddEntityFrameworkStores<LANCommander.Data.DatabaseContext>();
builder.Services.AddControllersWithViews();
@ -57,4 +59,4 @@ app.MapRazorPages();
if (!Directory.Exists("Upload"))
Directory.CreateDirectory("Upload");
app.Run();
app.Run();