Finished user administration

This commit is contained in:
2024-07-21 20:49:52 +02:00
parent f8ee78f1fd
commit 643ceeb607
10 changed files with 320 additions and 54 deletions

View File

@@ -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);

View File

@@ -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();

View 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";
}

View File

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

View File

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

View File

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

View File

@@ -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;
@@ -59,19 +140,135 @@
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;
}
} }

View File

@@ -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>
@if (_hasEditPrivileges || _hasDeletePrivileges) {
<th scope="col" style="user-select: none">Actions</th> <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>
@if (_hasEditPrivileges || _hasDeletePrivileges) {
<td> <td>
<div class="btn-group" role="group" aria-label="Basic example"> <div class="btn-group" role="group" aria-label="Basic example">
@if (_hasEditPrivileges) {
<button type="button" class="btn btn-warning" @onclick="() => EditUser(user)">Edit</button> <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> <button type="button" class="btn btn-danger" @onclick="() => Delete(user)">Delete</button>
}
</div> </div>
</td> </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
}); });

View File

@@ -43,10 +43,17 @@
[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);
} }
} }

View File

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