Add profile page for users to change username/email, download saves, change password.

This commit is contained in:
Pat Hartl 2023-09-18 00:58:40 -05:00
parent 07a41aeaf5
commit 8f853fab56
7 changed files with 295 additions and 5 deletions

View file

@ -24,6 +24,26 @@ namespace LANCommander.Areas.Identity.Pages.Account
_logger = logger;
}
public async Task<IActionResult> OnGet(string returnUrl = null, bool force = false)
{
if (force)
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
if (returnUrl != null)
{
return LocalRedirect(returnUrl);
}
else
{
return LocalRedirect("/Identity/Account/Login");
}
}
return Page();
}
public async Task<IActionResult> OnPost(string returnUrl = null)
{
await _signInManager.SignOutAsync();

View file

@ -0,0 +1,36 @@
using LANCommander.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace LANCommander.Controllers
{
[Authorize]
public class SavesController : Controller
{
private readonly GameSaveService GameSaveService;
public SavesController(GameSaveService gameSaveService)
{
GameSaveService = gameSaveService;
}
[HttpGet]
public async Task<IActionResult> Download(Guid id)
{
var save = await GameSaveService.Get(id);
if (User == null || User.Identity?.Name != save.User?.UserName)
return Unauthorized();
if (save == null)
return NotFound();
var filename = GameSaveService.GetSavePath(save);
if (!System.IO.File.Exists(filename))
return NotFound();
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/zip", $"{save.User?.UserName} - {save.Game?.Title} - {save.CreatedOn.ToString("MM-dd-yyyy.hh-mm")}");
}
}
}

View file

@ -0,0 +1,70 @@
@page "/Profile/ChangePassword"
@using Microsoft.AspNetCore.Components.Authorization;
@layout ProfileLayout
@inject UserManager<User> UserManager
@inject IMessageService MessageService
@inject AuthenticationStateProvider AuthenticationStateProvider
<PageHeader Title="Change Password" />
<div style="padding: 0 24px;">
<Form Model="Model" Layout="@FormLayout.Vertical">
<FormItem Label="Current Password">
<InputPassword @bind-Value="context.CurrentPassword" />
</FormItem>
<FormItem Label="New Password">
<InputPassword @bind-Value="context.NewPassword" />
</FormItem>
<FormItem Label="Confirm Password">
<InputPassword @bind-Value="context.NewPasswordConfirm" />
</FormItem>
<FormItem>
<Button OnClick="Change" Type="@ButtonType.Primary">Change</Button>
</FormItem>
</Form>
</div>
@code {
User User;
ChangePasswordModel Model = new ChangePasswordModel();
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated)
User = await UserManager.FindByNameAsync(authState.User.Identity.Name);
}
async void Change()
{
try
{
if (Model.NewPassword == Model.NewPasswordConfirm)
{
var result = await UserManager.ChangePasswordAsync(User, Model.CurrentPassword, Model.NewPassword);
if (result.Succeeded)
await MessageService.Success("Password changed!");
else
foreach (var error in result.Errors)
await MessageService.Error(error.Description);
}
else
await MessageService.Error("Passwords don't match!");
}
catch (Exception ex)
{
await MessageService.Error("Password could not be changed!");
}
}
public class ChangePasswordModel
{
public string CurrentPassword { get; set; }
public string NewPassword { get; set; }
public string NewPasswordConfirm { get; set; }
}
}

View file

@ -0,0 +1,60 @@
@page "/Profile"
@using Microsoft.AspNetCore.Components.Authorization;
@layout ProfileLayout
@inject UserManager<User> UserManager
@inject SignInManager<User> SignInManager
@inject IMessageService MessageService
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject NavigationManager NavigationManager
@inject IJSRuntime JSRuntime
<PageHeader Title="Profile" />
<div style="padding: 0 24px;">
<p>Changing your username or email will force you to log out.</p>
<Form @ref="Form" Model="User" Layout="@FormLayout.Vertical" OnFinish="Save" ValidateOnChange="true">
<FormItem Label="Username">
<Input @bind-Value="context.UserName" />
</FormItem>
<FormItem Label="Email Address">
<Input @bind-Value="context.Email" />
</FormItem>
<FormItem>
<Button HtmlType="submit" Type="@ButtonType.Primary" Disabled="!Form.IsModified">Save</Button>
</FormItem>
</Form>
</div>
@code {
User User = new User();
Form<User> Form;
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated)
User = await UserManager.FindByNameAsync(authState.User.Identity.Name);
}
private async Task Save()
{
try
{
if (Form.IsModified)
{
await UserManager.UpdateAsync(User);
MessageService.Success("Profile updated!");
NavigationManager.NavigateTo("/Identity/Account/Logout?force=true", true);
}
}
catch (Exception ex)
{
await MessageService.Error("An unknown error occurred.");
}
}
}

View file

@ -0,0 +1,16 @@
@inherits LayoutComponentBase
@layout MainLayout
<Layout Class="panel-layout" Style="padding: 24px 0;">
<Sider Width="200">
<Menu Mode=@MenuMode.Inline Style="height: 100%">
<MenuItem RouterLink="/Profile">General</MenuItem>
<MenuItem RouterLink="/Profile/ChangePassword">Change Password</MenuItem>
<MenuItem RouterLink="/Profile/Saves">Saves</MenuItem>
</Menu>
</Sider>
<Content>
@Body
</Content>
</Layout>

View file

@ -0,0 +1,69 @@
@page "/Profile/Saves"
@using LANCommander.Models;
@using Microsoft.AspNetCore.Components.Authorization;
@using Microsoft.EntityFrameworkCore;
@layout ProfileLayout
@inject UserManager<User> UserManager
@inject IMessageService MessageService
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject GameSaveService GameSaveService
@inject IJSRuntime JSRuntime
<PageHeader Title="Game Saves" />
<div style="padding: 0 24px;">
<Table TItem="GameSave" DataSource="@GameSaves">
<PropertyColumn Property="s => s.Game.Title" Sortable />
<PropertyColumn Property="s => s.CreatedOn" Format="MM/dd/yyyy hh:mm tt" Sortable />
<ActionColumn Title="">
<Space Direction="DirectionVHType.Horizontal">
<SpaceItem>
<Button Icon="@IconType.Outline.Download" Type="@ButtonType.Text" OnClick="() => Download(context.Id)" />
</SpaceItem>
<SpaceItem>
<Popconfirm OnConfirm="() => Delete(context)" Title="Are you sure you want to delete this game save?">
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
</Popconfirm>
</SpaceItem>
</Space>
</ActionColumn>
</Table>
</div>
@code {
User User;
ICollection<GameSave> GameSaves = new List<GameSave>();
bool Loading = true;
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated)
User = await UserManager.FindByNameAsync(authState.User.Identity.Name);
if (User != null)
GameSaves = User.GameSaves.OrderBy(s => s.Game.Title).ThenBy(s => s.CreatedOn).ToList();
Loading = false;
}
private async Task Download(Guid id)
{
await JSRuntime.InvokeAsync<object>("open", $"/Saves/Download/{id}", "_blank");
}
private async Task Delete(GameSave gameSave)
{
GameSaves = new List<GameSave>();
Loading = true;
await GameSaveService.Delete(gameSave);
GameSaves = await GameSaveService.Get(gs => gs.UserId == User.Id).OrderBy(gs => gs.Game.Title).ThenByDescending(gs => gs.UpdatedOn).ToListAsync();
Loading = false;
}
}

View file

@ -1,15 +1,34 @@
@inherits LayoutComponentBase
@using Microsoft.AspNetCore.Components.Authorization;
@using System.Security.Claims;
@inject AuthenticationStateProvider AuthenticationStateProvider
<Layout Class="layout">
<MainMenu>
<MenuItem RouterLink="/Dashboard">Dashboard</MenuItem>
@if (User != null && User.IsInRole("Administrator"))
{
<MenuItem RouterLink="/Games">Games</MenuItem>
<MenuItem RouterLink="/Servers">Servers</MenuItem>
<MenuItem RouterLink="/Files">Files</MenuItem>
<MenuItem RouterLink="/Settings">Settings</MenuItem>
}
<MenuItem RouterLink="/Profile">Profile</MenuItem>
</MainMenu>
<Content Style="padding: 24px; min-height: 100vh;">
@Body
</Content>
</Layout>
@code {
ClaimsPrincipal User;
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated)
User = authState.User;
}
}