Finished user administration
This commit is contained in:
@@ -12,10 +12,14 @@ public interface IPermissionService {
|
|||||||
|
|
||||||
Task<IList<PermissionGroup>> GetUserPermissionGroups(User user);
|
Task<IList<PermissionGroup>> GetUserPermissionGroups(User user);
|
||||||
|
|
||||||
|
Task RemoveGroupFromUser(User user, PermissionGroup group);
|
||||||
|
|
||||||
Task CreatePermissionGroup(string name, bool isDefault = false, string description = null);
|
Task CreatePermissionGroup(string name, bool isDefault = false, string description = null);
|
||||||
|
|
||||||
Task DeletePermissionGroup(PermissionGroup group);
|
Task DeletePermissionGroup(PermissionGroup group);
|
||||||
|
|
||||||
|
Task<Permission> GetPermission(string name, IPermissionOwner owner);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// permission system:<br/>
|
/// permission system:<br/>
|
||||||
/// - "*" -> all rights<br/>
|
/// - "*" -> all rights<br/>
|
||||||
@@ -28,7 +32,7 @@ public interface IPermissionService {
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task AddPermission(IPermissionOwner owner, string permission);
|
Task AddPermission(IPermissionOwner owner, string permission);
|
||||||
|
|
||||||
Task DeletePermission(Permission permission);
|
Task RemovePermission(Permission permission);
|
||||||
|
|
||||||
Task<string[]> GetFullPermissions(string user);
|
Task<string[]> GetFullPermissions(string user);
|
||||||
|
|
||||||
|
|||||||
@@ -58,11 +58,22 @@ internal sealed class PermissionService<TDbContext>(TDbContext context, ITokenCo
|
|||||||
var perms = await GetFullPermissions(user.Id.ToString());
|
var perms = await GetFullPermissions(user.Id.ToString());
|
||||||
|
|
||||||
return groups
|
return groups
|
||||||
.Where(group => PermissionValidator.IncludesPermission(group.Name, perms))
|
.Where(group => perms.Contains(group.Name))
|
||||||
.Select(group => group.ToPermissionGroup(context))
|
.Select(group => group.ToPermissionGroup(context))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task RemoveGroupFromUser(User user, PermissionGroup group) {
|
||||||
|
var entry = await context.Permissions
|
||||||
|
.Where(perm => perm.PermissionText == group.Name && perm.UserId == user.Id.ToString())
|
||||||
|
.SingleOrDefaultAsync();
|
||||||
|
|
||||||
|
if (entry is null) return;
|
||||||
|
|
||||||
|
context.Permissions.Remove(entry);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task CreatePermissionGroup(string name, bool isDefault = false, string description = null) {
|
public async Task CreatePermissionGroup(string name, bool isDefault = false, string description = null) {
|
||||||
var group = new GroupEntry {
|
var group = new GroupEntry {
|
||||||
Name = name,
|
Name = name,
|
||||||
@@ -81,6 +92,15 @@ internal sealed class PermissionService<TDbContext>(TDbContext context, ITokenCo
|
|||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Permission> GetPermission(string name, IPermissionOwner owner) {
|
||||||
|
var ownerId = (owner is User user) ? user.Id.ToString() : ((PermissionGroup)owner).Name;
|
||||||
|
|
||||||
|
return await context.Permissions
|
||||||
|
.Where(perm => perm.PermissionText == name && perm.UserId == ownerId)
|
||||||
|
.Select(perm => perm.ToPermissionModel())
|
||||||
|
.SingleOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task AddPermission(IPermissionOwner owner, string permission) {
|
public async Task AddPermission(IPermissionOwner owner, string permission) {
|
||||||
var userId = owner is User user ? user.Id.ToString() : (owner as PermissionGroup)?.Name;
|
var userId = owner is User user ? user.Id.ToString() : (owner as PermissionGroup)?.Name;
|
||||||
|
|
||||||
@@ -92,7 +112,7 @@ internal sealed class PermissionService<TDbContext>(TDbContext context, ITokenCo
|
|||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeletePermission(Permission permission) {
|
public async Task RemovePermission(Permission permission) {
|
||||||
var entry = await context.Permissions.SingleOrDefaultAsync(entry => entry.RecordId == permission.Id);
|
var entry = await context.Permissions.SingleOrDefaultAsync(entry => entry.RecordId == permission.Id);
|
||||||
context.Permissions.Remove(entry);
|
context.Permissions.Remove(entry);
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
|
|||||||
8
HopFrame.Web/AdminPermissions.cs
Normal file
8
HopFrame.Web/AdminPermissions.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace HopFrame.Web;
|
||||||
|
|
||||||
|
public static class AdminPermissions {
|
||||||
|
public const string IsAdmin = "hopframe.admin";
|
||||||
|
public const string ViewUsers = "hopframe.admin.users.view";
|
||||||
|
public const string EditUsers = "hopframe.admin.users.edit";
|
||||||
|
public const string DeleteUsers = "hopframe.admin.users.delete";
|
||||||
|
}
|
||||||
@@ -10,19 +10,3 @@
|
|||||||
Navigator.NavigateTo("administration/users");
|
Navigator.NavigateTo("administration/users");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="sub-layout">
|
|
||||||
<div class="content">
|
|
||||||
<Switch>
|
|
||||||
<Route Template="administration/users">
|
|
||||||
<UsersPage/>
|
|
||||||
</Route>
|
|
||||||
<Route Template="administration/user/{UserId}">
|
|
||||||
<UserEditPage/>
|
|
||||||
</Route>
|
|
||||||
<Route>
|
|
||||||
<p>No content found in nested layout</p>
|
|
||||||
</Route>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@using HopFrame.Web.Components
|
@using HopFrame.Web.Components
|
||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
<AuthorizedView Permission="HopFrame.Admin" RedirectIfUnauthorized="login" />
|
<AuthorizedView Permission="@AdminPermissions.IsAdmin" RedirectIfUnauthorized="login?redirect=/administration" />
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<input type="checkbox" title="Navigation menu" class="navbar-toggler"/>
|
<input type="checkbox" title="Navigation menu" class="navbar-toggler"/>
|
||||||
|
|
||||||
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
|
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
|
||||||
<nav class="flex-column">
|
<nav class="flex-column" style="display: flex; height: 100%">
|
||||||
<div class="nav-item px-3">
|
<div class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="administration/users">
|
<NavLink class="nav-link" href="administration/users">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-fill" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-fill" viewBox="0 0 16 16">
|
||||||
@@ -24,5 +24,14 @@
|
|||||||
</svg> Groups
|
</svg> Groups
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-item px-3" style="margin-top: auto">
|
||||||
|
<NavLink class="nav-link" href="login">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-left" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M6 12.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v2a.5.5 0 0 1-1 0v-2A1.5 1.5 0 0 1 6.5 2h8A1.5 1.5 0 0 1 16 3.5v9a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 5 12.5v-2a.5.5 0 0 1 1 0z"/>
|
||||||
|
<path fill-rule="evenodd" d="M.146 8.354a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L1.707 7.5H10.5a.5.5 0 0 1 0 1H1.707l2.147 2.146a.5.5 0 0 1-.708.708z"/>
|
||||||
|
</svg> Logout
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
@page "/administration/user/{UserId}"
|
@page "/administration/user/{UserId}"
|
||||||
|
|
||||||
|
@using CurrieTechnologies.Razor.SweetAlert2
|
||||||
@using HopFrame.Database.Models
|
@using HopFrame.Database.Models
|
||||||
@using HopFrame.Security.Services
|
@using HopFrame.Security.Services
|
||||||
@using HopFrame.Web.Pages.Administration.Layout
|
@using HopFrame.Web.Pages.Administration.Layout
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using HopFrame.Web.Components
|
||||||
|
|
||||||
@layout AdminLayout
|
@layout AdminLayout
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
|
||||||
<PageTitle>Edit @User.Username</PageTitle>
|
<PageTitle>Edit @User.Username</PageTitle>
|
||||||
|
<AuthorizedView Permission="@AdminPermissions.EditUsers" RedirectIfUnauthorized="@ConstructRedirectUrl()"/>
|
||||||
|
|
||||||
<h3>Edit @User.Username (@User.Id)</h3>
|
<h3>Edit @User.Username (@User.Id)</h3>
|
||||||
|
|
||||||
<EditForm EditContext="_context" OnValidSubmit="OnEdit" FormName="register-form">
|
<EditForm EditContext="_context" OnValidSubmit="OnEdit" FormName="register-form" class="edit-form">
|
||||||
@*<AntiforgeryToken />*@
|
@*<AntiforgeryToken />*@
|
||||||
<div class="field-wrapper">
|
<div class="field-wrapper" style="max-width: 750px">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="id" class="form-label">Registered At</label>
|
<label for="id" class="form-label">Registered At</label>
|
||||||
<input type="text" class="form-control" id="id" disabled value="@User.CreatedAt"/>
|
<input type="text" class="form-control" id="id" disabled value="@User.CreatedAt"/>
|
||||||
@@ -31,25 +34,103 @@
|
|||||||
<InputText type="text" class="form-control" id="username" required @bind-Value="User.Username"/>
|
<InputText type="text" class="form-control" id="username" required @bind-Value="User.Username"/>
|
||||||
<ValidationMessage For="() => User.Username"/>
|
<ValidationMessage For="() => User.Username"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="groups" class="form-label">Groups</label>
|
||||||
|
<ul class="list-group" id="groups">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
@foreach (var group in _groups) {
|
||||||
|
<li class="list-group-item">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm" style="margin-right: 15px" @onclick="() => RemoveGroup(group)">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
|
||||||
|
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span>@group.Name.Replace("group.", "")</span>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div style="display: flex; gap: 20px">
|
||||||
|
<select class="form-select" aria-label="Add group to user" id="add-group" @bind="_selectedGroup">
|
||||||
|
<option selected>Select group</option>
|
||||||
|
|
||||||
|
@foreach (var group in _allGroups) {
|
||||||
|
if (_groups.All(g => g.Name != group.Name)) {
|
||||||
|
<option value="@group.Name">@group.Name.Replace("group.", "")</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="AddGroup">Add</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="permissions" class="form-label">Permissions</label>
|
||||||
|
<ul class="list-group" id="permissions">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
@foreach (var perm in User.Permissions.Where(perm => !perm.PermissionName.StartsWith("group."))) {
|
||||||
|
<li class="list-group-item">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm" style="margin-right: 15px" @onclick="() => RemovePermission(perm)">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
|
||||||
|
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span>@perm.PermissionName</span>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div style="display: flex; gap: 20px">
|
||||||
|
<input type="text" class="form-control" placeholder="New permission" @bind="_permissionToAdd">
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="AddPermission">Add</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Edit</button>
|
<button type="submit" class="btn btn-primary">Edit</button>
|
||||||
<button type="reset" class="btn btn-secondary">Cancel</button>
|
<button type="reset" class="btn btn-secondary" @onclick="Back">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</EditForm>
|
</EditForm>
|
||||||
|
|
||||||
@inject IUserService Users
|
@inject IUserService Users
|
||||||
|
@inject IPermissionService Permissions
|
||||||
|
@inject NavigationManager Navigator
|
||||||
|
@inject SweetAlertService Alerts
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter] public string UserId { get; set; }
|
||||||
public string UserId { get; set; }
|
|
||||||
|
|
||||||
private EditContext _context;
|
private EditContext _context;
|
||||||
private ValidationMessageStore _messages;
|
private ValidationMessageStore _messages;
|
||||||
|
|
||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm] public User User { get; set; }
|
||||||
public User User { get; set; }
|
|
||||||
|
private IList<PermissionGroup> _groups = new List<PermissionGroup>();
|
||||||
|
private IList<PermissionGroup> _allGroups = new List<PermissionGroup>();
|
||||||
|
private string _selectedGroup;
|
||||||
|
private string _permissionToAdd;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() {
|
protected override async Task OnInitializedAsync() {
|
||||||
User = await Users.GetUser(Guid.Parse(UserId));
|
if (Guid.TryParse(UserId, out var guid)) {
|
||||||
|
User = await Users.GetUser(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (User is null) {
|
||||||
|
Navigator.NavigateTo("/administration/users");
|
||||||
|
}
|
||||||
|
|
||||||
|
_groups = await Permissions.GetUserPermissionGroups(User);
|
||||||
|
_allGroups = await Permissions.GetPermissionGroups();
|
||||||
|
|
||||||
_context = new EditContext(User);
|
_context = new EditContext(User);
|
||||||
_context.OnValidationRequested += ValidateForm;
|
_context.OnValidationRequested += ValidateForm;
|
||||||
@@ -58,20 +139,136 @@
|
|||||||
|
|
||||||
private async Task OnEdit() {
|
private async Task OnEdit() {
|
||||||
var hasConflict = false;
|
var hasConflict = false;
|
||||||
|
|
||||||
if (await Users.GetUserByEmail(User.Email) is not null) {
|
var userByEmail = await Users.GetUserByEmail(User.Email);
|
||||||
|
if (userByEmail is not null && userByEmail.Id != User.Id) {
|
||||||
_messages.Add(() => User.Email, "Email is already in use");
|
_messages.Add(() => User.Email, "Email is already in use");
|
||||||
hasConflict = true;
|
hasConflict = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await Users.GetUserByUsername(User.Username) is not null) {
|
var userByUsername = await Users.GetUserByUsername(User.Username);
|
||||||
|
if (userByUsername is not null && userByUsername.Id != User.Id) {
|
||||||
_messages.Add(() => User.Username, "Username is already in use");
|
_messages.Add(() => User.Username, "Username is already in use");
|
||||||
hasConflict = true;
|
hasConflict = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasConflict) return;
|
if (hasConflict) return;
|
||||||
|
|
||||||
|
var result = await Alerts.FireAsync(new SweetAlertOptions {
|
||||||
|
Title = "Are you sure?",
|
||||||
|
Icon = SweetAlertIcon.Warning,
|
||||||
|
ConfirmButtonText = "Yes",
|
||||||
|
ShowCancelButton = true,
|
||||||
|
ShowConfirmButton = true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.IsConfirmed) {
|
||||||
|
await Users.UpdateUser(User);
|
||||||
|
|
||||||
|
await Alerts.FireAsync(new SweetAlertOptions {
|
||||||
|
Title = "User edited!",
|
||||||
|
Icon = SweetAlertIcon.Success,
|
||||||
|
Timer = 1500,
|
||||||
|
ShowConfirmButton = false
|
||||||
|
});
|
||||||
|
|
||||||
|
Back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Back() {
|
||||||
|
Navigator.NavigateTo("/administration/users");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveGroup(PermissionGroup group) {
|
||||||
|
var result = await Alerts.FireAsync(new SweetAlertOptions {
|
||||||
|
Title = "Are you sure?",
|
||||||
|
Icon = SweetAlertIcon.Warning,
|
||||||
|
ConfirmButtonText = "Yes",
|
||||||
|
ShowCancelButton = true,
|
||||||
|
ShowConfirmButton = true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.IsConfirmed) {
|
||||||
|
await Permissions.RemoveGroupFromUser(User, group);
|
||||||
|
_groups.Remove(group);
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
await Alerts.FireAsync(new SweetAlertOptions {
|
||||||
|
Title = "Group removed!",
|
||||||
|
Icon = SweetAlertIcon.Success,
|
||||||
|
Timer = 1500,
|
||||||
|
ShowConfirmButton = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemovePermission(Permission perm) {
|
||||||
|
var result = await Alerts.FireAsync(new SweetAlertOptions {
|
||||||
|
Title = "Are you sure?",
|
||||||
|
Icon = SweetAlertIcon.Warning,
|
||||||
|
ConfirmButtonText = "Yes",
|
||||||
|
ShowCancelButton = true,
|
||||||
|
ShowConfirmButton = true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.IsConfirmed) {
|
||||||
|
await Permissions.RemovePermission(perm);
|
||||||
|
User.Permissions.Remove(perm);
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
await Alerts.FireAsync(new SweetAlertOptions {
|
||||||
|
Title = "Permission removed!",
|
||||||
|
Icon = SweetAlertIcon.Success,
|
||||||
|
Timer = 1500,
|
||||||
|
ShowConfirmButton = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddGroup() {
|
||||||
|
if (string.IsNullOrWhiteSpace(_selectedGroup)) {
|
||||||
|
await Alerts.FireAsync(new SweetAlertOptions {
|
||||||
|
Title = "Select a group!",
|
||||||
|
Icon = SweetAlertIcon.Error,
|
||||||
|
ShowConfirmButton = true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var group = _allGroups.SingleOrDefault(group => group.Name == _selectedGroup);
|
||||||
|
|
||||||
|
await Permissions.AddPermission(User, group?.Name);
|
||||||
|
_groups.Add(group);
|
||||||
|
|
||||||
|
await Alerts.FireAsync(new SweetAlertOptions {
|
||||||
|
Title = "Group added!",
|
||||||
|
Icon = SweetAlertIcon.Success,
|
||||||
|
Timer = 1500,
|
||||||
|
ShowConfirmButton = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddPermission() {
|
||||||
|
if (string.IsNullOrWhiteSpace(_permissionToAdd)) {
|
||||||
|
await Alerts.FireAsync(new SweetAlertOptions {
|
||||||
|
Title = "Enter a permission name!",
|
||||||
|
Icon = SweetAlertIcon.Error,
|
||||||
|
ShowConfirmButton = true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Permissions.AddPermission(User, _permissionToAdd);
|
||||||
|
User.Permissions.Add(await Permissions.GetPermission(_permissionToAdd, User));
|
||||||
|
_permissionToAdd = "";
|
||||||
|
|
||||||
|
await Alerts.FireAsync(new SweetAlertOptions {
|
||||||
|
Title = "Permission added!",
|
||||||
|
Icon = SweetAlertIcon.Success,
|
||||||
|
Timer = 1500,
|
||||||
|
ShowConfirmButton = false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateForm(object sender, ValidationRequestedEventArgs e) {
|
private void ValidateForm(object sender, ValidationRequestedEventArgs e) {
|
||||||
@@ -81,4 +278,9 @@
|
|||||||
_messages.Add(() => User.Email, "Please enter a valid email address");
|
_messages.Add(() => User.Email, "Please enter a valid email address");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ConstructRedirectUrl() {
|
||||||
|
return "login?redirect=" + Navigator.Uri;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
@using CurrieTechnologies.Razor.SweetAlert2
|
@using CurrieTechnologies.Razor.SweetAlert2
|
||||||
@using HopFrame.Database.Models
|
@using HopFrame.Database.Models
|
||||||
|
@using HopFrame.Security.Claims
|
||||||
@using HopFrame.Security.Services
|
@using HopFrame.Security.Services
|
||||||
@using HopFrame.Web.Pages.Administration.Layout
|
@using HopFrame.Web.Pages.Administration.Layout
|
||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
@using HopFrame.Web.Components
|
@using HopFrame.Web.Components
|
||||||
|
|
||||||
<PageTitle>Users</PageTitle>
|
<PageTitle>Users</PageTitle>
|
||||||
<AuthorizedView Permission="HopFrame.Admin.Users.View" RedirectIfUnauthorized="login"/>
|
<AuthorizedView Permission="@AdminPermissions.ViewUsers" RedirectIfUnauthorized="login?redirect=/administration/users"/>
|
||||||
|
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h3 style="user-select: none">
|
<h3 style="user-select: none">
|
||||||
@@ -82,7 +83,10 @@
|
|||||||
}
|
}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" style="user-select: none">Primary Group</th>
|
<th scope="col" style="user-select: none">Primary Group</th>
|
||||||
<th scope="col" style="user-select: none">Actions</th>
|
|
||||||
|
@if (_hasEditPrivileges || _hasDeletePrivileges) {
|
||||||
|
<th scope="col" style="user-select: none">Actions</th>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -93,12 +97,20 @@
|
|||||||
<td>@user.Username</td>
|
<td>@user.Username</td>
|
||||||
<td>@user.CreatedAt</td>
|
<td>@user.CreatedAt</td>
|
||||||
<td>@GetFriendlyGroupName(user)</td>
|
<td>@GetFriendlyGroupName(user)</td>
|
||||||
<td>
|
|
||||||
<div class="btn-group" role="group" aria-label="Basic example">
|
@if (_hasEditPrivileges || _hasDeletePrivileges) {
|
||||||
<button type="button" class="btn btn-warning" @onclick="() => EditUser(user)">Edit</button>
|
<td>
|
||||||
<button type="button" class="btn btn-danger" @onclick="() => Delete(user)">Delete</button>
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
</div>
|
@if (_hasEditPrivileges) {
|
||||||
</td>
|
<button type="button" class="btn btn-warning" @onclick="() => EditUser(user)">Edit</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (_hasDeletePrivileges) {
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="() => Delete(user)">Delete</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -108,6 +120,7 @@
|
|||||||
@inject IPermissionService PermissionsService
|
@inject IPermissionService PermissionsService
|
||||||
@inject NavigationManager Navigator
|
@inject NavigationManager Navigator
|
||||||
@inject SweetAlertService Alerts
|
@inject SweetAlertService Alerts
|
||||||
|
@inject ITokenContext Auth
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private IList<User> _users = new List<User>();
|
private IList<User> _users = new List<User>();
|
||||||
@@ -118,6 +131,9 @@
|
|||||||
|
|
||||||
private string _searchText;
|
private string _searchText;
|
||||||
|
|
||||||
|
private bool _hasEditPrivileges = false;
|
||||||
|
private bool _hasDeletePrivileges = false;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() {
|
protected override async Task OnInitializedAsync() {
|
||||||
_users = await UserService.GetUsers();
|
_users = await UserService.GetUsers();
|
||||||
|
|
||||||
@@ -125,6 +141,9 @@
|
|||||||
var groups = await PermissionsService.GetUserPermissionGroups(user);
|
var groups = await PermissionsService.GetUserPermissionGroups(user);
|
||||||
_userGroups.Add(user.Id, groups.FirstOrDefault());
|
_userGroups.Add(user.Id, groups.FirstOrDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_hasEditPrivileges = await PermissionsService.HasPermission(AdminPermissions.EditUsers, Auth.User.Id);
|
||||||
|
_hasDeletePrivileges = await PermissionsService.HasPermission(AdminPermissions.DeleteUsers, Auth.User.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Reload() {
|
private void Reload() {
|
||||||
@@ -177,6 +196,7 @@
|
|||||||
Title = "Are you sure?",
|
Title = "Are you sure?",
|
||||||
Text = "You won't be able to revert this!",
|
Text = "You won't be able to revert this!",
|
||||||
Icon = SweetAlertIcon.Warning,
|
Icon = SweetAlertIcon.Warning,
|
||||||
|
ConfirmButtonText = "Yes",
|
||||||
ShowCancelButton = true,
|
ShowCancelButton = true,
|
||||||
ShowConfirmButton = true
|
ShowConfirmButton = true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,11 +42,18 @@
|
|||||||
@code {
|
@code {
|
||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm]
|
||||||
private UserLogin LoginData { get; set; }
|
private UserLogin LoginData { get; set; }
|
||||||
|
|
||||||
|
[SupplyParameterFromQuery(Name = "redirect")]
|
||||||
|
private string RedirectAfter { get; set; }
|
||||||
|
|
||||||
private bool _loginError;
|
private bool _loginError;
|
||||||
|
|
||||||
protected override void OnInitialized() {
|
protected override async Task OnInitializedAsync() {
|
||||||
LoginData ??= new();
|
LoginData ??= new();
|
||||||
|
|
||||||
|
if (await Auth.IsLoggedIn()) {
|
||||||
|
await Auth.Logout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnLogin() {
|
private async Task OnLogin() {
|
||||||
@@ -57,6 +64,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigator.NavigateTo(Register.RedirectAfterRegister, true);
|
Navigator.NavigateTo(string.IsNullOrEmpty(RedirectAfter) ? Register.RedirectAfterRegister : RedirectAfter, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,15 +101,6 @@ public class AuthService<TDbContext>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TokenEntry> RefreshLogin() {
|
public async Task<TokenEntry> RefreshLogin() {
|
||||||
if (await IsLoggedIn()) {
|
|
||||||
var oldToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.AccessTokenType];
|
|
||||||
var entry = await context.Tokens.SingleOrDefaultAsync(token => token.Token == oldToken);
|
|
||||||
|
|
||||||
if (entry is not null) {
|
|
||||||
context.Tokens.Remove(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var refreshToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.RefreshTokenType];
|
var refreshToken = httpAccessor.HttpContext?.Request.Cookies[ITokenContext.RefreshTokenType];
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(refreshToken)) return null;
|
if (string.IsNullOrWhiteSpace(refreshToken)) return null;
|
||||||
@@ -117,6 +108,27 @@ public class AuthService<TDbContext>(
|
|||||||
var token = await context.Tokens.SingleOrDefaultAsync(token => token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType);
|
var token = await context.Tokens.SingleOrDefaultAsync(token => token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType);
|
||||||
|
|
||||||
if (token is null) return null;
|
if (token is null) return null;
|
||||||
|
|
||||||
|
var oldAccessTokens = context.Tokens
|
||||||
|
.AsEnumerable()
|
||||||
|
.Where(old =>
|
||||||
|
old.Type == TokenEntry.AccessTokenType &&
|
||||||
|
old.UserId == token.UserId &&
|
||||||
|
old.CreatedAt + HopFrameAuthentication<TDbContext>.AccessTokenTime < DateTime.Now)
|
||||||
|
.ToList();
|
||||||
|
context.Tokens.RemoveRange(oldAccessTokens);
|
||||||
|
|
||||||
|
var oldRefreshTokens = context.Tokens
|
||||||
|
.AsEnumerable()
|
||||||
|
.Where(old =>
|
||||||
|
old.Type == TokenEntry.RefreshTokenType &&
|
||||||
|
old.UserId == token.UserId &&
|
||||||
|
old.CreatedAt + HopFrameAuthentication<TDbContext>.RefreshTokenTime < DateTime.Now)
|
||||||
|
.ToList();
|
||||||
|
context.Tokens.RemoveRange(oldRefreshTokens);
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
if (token.CreatedAt + HopFrameAuthentication<TDbContext>.RefreshTokenTime < DateTime.Now) return null;
|
if (token.CreatedAt + HopFrameAuthentication<TDbContext>.RefreshTokenTime < DateTime.Now) return null;
|
||||||
|
|
||||||
var accessToken = new TokenEntry {
|
var accessToken = new TokenEntry {
|
||||||
|
|||||||
Reference in New Issue
Block a user