mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Add Created date to user, add verified column in list and make user list use same model as modern lists
This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace BTCPayServer.Client.Models
|
namespace BTCPayServer.Client.Models
|
||||||
{
|
{
|
||||||
public class ApplicationUserData
|
public class ApplicationUserData
|
||||||
@@ -26,5 +29,11 @@ namespace BTCPayServer.Client.Models
|
|||||||
/// the roles of the user
|
/// the roles of the user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] Roles { get; set; }
|
public string[] Roles { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the date the user was created. Null if created before v1.0.5.6.
|
||||||
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||||
|
public DateTimeOffset? Created { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
@@ -26,5 +27,6 @@ namespace BTCPayServer.Data
|
|||||||
|
|
||||||
public List<U2FDevice> U2FDevices { get; set; }
|
public List<U2FDevice> U2FDevices { get; set; }
|
||||||
public List<APIKeyData> APIKeys { get; set; }
|
public List<APIKeyData> APIKeys { get; set; }
|
||||||
|
public DateTimeOffset? Created { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20201002145033_AddCreateDateToUser")]
|
||||||
|
public partial class AddCreateDateToUser : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||||
|
name: "Created",
|
||||||
|
table: "AspNetUsers",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Created",
|
||||||
|
table: "AspNetUsers");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,6 +108,9 @@ namespace BTCPayServer.Migrations
|
|||||||
.IsConcurrencyToken()
|
.IsConcurrencyToken()
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("Created")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Email")
|
b.Property<string>("Email")
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasMaxLength(256);
|
.HasMaxLength(256);
|
||||||
@@ -525,7 +528,8 @@ namespace BTCPayServer.Migrations
|
|||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("PullPaymentDataId")
|
b.Property<string>("PullPaymentDataId")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(30);
|
||||||
|
|
||||||
b.HasKey("InvoiceDataId", "PullPaymentDataId");
|
b.HasKey("InvoiceDataId", "PullPaymentDataId");
|
||||||
|
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ namespace BTCPayServer.Tests
|
|||||||
IsAdministrator = true
|
IsAdministrator = true
|
||||||
});
|
});
|
||||||
Assert.Contains("ServerAdmin", admin.Roles);
|
Assert.Contains("ServerAdmin", admin.Roles);
|
||||||
|
Assert.NotNull(admin.Created);
|
||||||
|
Assert.True((DateTimeOffset.Now - admin.Created).Value.Seconds < 10);
|
||||||
|
|
||||||
// Creating a new user without proper creds is now impossible (unauthorized)
|
// Creating a new user without proper creds is now impossible (unauthorized)
|
||||||
// Because if registration are locked and that an admin exists, we don't accept unauthenticated connection
|
// Because if registration are locked and that an admin exists, we don't accept unauthenticated connection
|
||||||
|
|||||||
@@ -426,7 +426,8 @@ namespace BTCPayServer.Controllers
|
|||||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, RequiresEmailConfirmation = policies.RequiresConfirmedEmail };
|
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, RequiresEmailConfirmation = policies.RequiresConfirmedEmail,
|
||||||
|
Created = DateTimeOffset.UtcNow };
|
||||||
var result = await _userManager.CreateAsync(user, model.Password);
|
var result = await _userManager.CreateAsync(user, model.Password);
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -110,7 +111,8 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
{
|
{
|
||||||
UserName = request.Email,
|
UserName = request.Email,
|
||||||
Email = request.Email,
|
Email = request.Email,
|
||||||
RequiresEmailConfirmation = policies.RequiresConfirmedEmail
|
RequiresEmailConfirmation = policies.RequiresConfirmedEmail,
|
||||||
|
Created = DateTimeOffset.UtcNow,
|
||||||
};
|
};
|
||||||
var passwordValidation = await this._passwordValidator.ValidateAsync(_userManager, user, request.Password);
|
var passwordValidation = await this._passwordValidator.ValidateAsync(_userManager, user, request.Password);
|
||||||
if (!passwordValidation.Succeeded)
|
if (!passwordValidation.Succeeded)
|
||||||
@@ -165,7 +167,8 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
Email = data.Email,
|
Email = data.Email,
|
||||||
EmailConfirmed = data.EmailConfirmed,
|
EmailConfirmed = data.EmailConfirmed,
|
||||||
RequiresEmailConfirmation = data.RequiresEmailConfirmation,
|
RequiresEmailConfirmation = data.RequiresEmailConfirmation,
|
||||||
Roles = roles
|
Roles = roles,
|
||||||
|
Created = data.Created
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,26 +10,30 @@ using BTCPayServer.Models.ServerViewModels;
|
|||||||
using BTCPayServer.Storage.Services;
|
using BTCPayServer.Storage.Services;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
public partial class ServerController
|
public partial class ServerController
|
||||||
{
|
{
|
||||||
[Route("server/users")]
|
[Route("server/users")]
|
||||||
public IActionResult ListUsers(int skip = 0, int count = 50)
|
public async Task<IActionResult> ListUsers(UsersViewModel model)
|
||||||
{
|
{
|
||||||
var users = new UsersViewModel();
|
model = this.ParseListQuery(model ?? new UsersViewModel());
|
||||||
users.Users = _UserManager.Users.Skip(skip).Take(count)
|
var users = _UserManager.Users;
|
||||||
|
model.Total = await users.CountAsync();
|
||||||
|
model.Users = await users
|
||||||
|
.Skip(model.Skip).Take(model.Count)
|
||||||
.Select(u => new UsersViewModel.UserViewModel
|
.Select(u => new UsersViewModel.UserViewModel
|
||||||
{
|
{
|
||||||
Name = u.UserName,
|
Name = u.UserName,
|
||||||
Email = u.Email,
|
Email = u.Email,
|
||||||
Id = u.Id
|
Id = u.Id,
|
||||||
}).ToList();
|
Verified = u.EmailConfirmed || !u.RequiresEmailConfirmation,
|
||||||
users.Skip = skip;
|
Created = u.Created
|
||||||
users.Count = count;
|
}).ToListAsync();
|
||||||
users.Total = _UserManager.Users.Count();
|
|
||||||
return View(users);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("server/users/{userId}")]
|
[Route("server/users/{userId}")]
|
||||||
@@ -39,15 +43,16 @@ namespace BTCPayServer.Controllers
|
|||||||
if (user == null)
|
if (user == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var roles = await _UserManager.GetRolesAsync(user);
|
var roles = await _UserManager.GetRolesAsync(user);
|
||||||
var userVM = new UserViewModel();
|
var userVM = new UsersViewModel.UserViewModel
|
||||||
userVM.Id = user.Id;
|
{
|
||||||
userVM.Email = user.Email;
|
Id = user.Id,
|
||||||
userVM.IsAdmin = IsAdmin(roles);
|
Email = user.Email,
|
||||||
|
Verified = user.EmailConfirmed || !user.RequiresEmailConfirmation,
|
||||||
|
IsAdmin = IsAdmin(roles)
|
||||||
|
};
|
||||||
return View(userVM);
|
return View(userVM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static bool IsAdmin(IList<string> roles)
|
private static bool IsAdmin(IList<string> roles)
|
||||||
{
|
{
|
||||||
return roles.Contains(Roles.ServerAdmin, StringComparer.Ordinal);
|
return roles.Contains(Roles.ServerAdmin, StringComparer.Ordinal);
|
||||||
@@ -55,7 +60,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
[Route("server/users/{userId}")]
|
[Route("server/users/{userId}")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public new async Task<IActionResult> User(string userId, UserViewModel viewModel)
|
public new async Task<IActionResult> User(string userId, UsersViewModel.UserViewModel viewModel)
|
||||||
{
|
{
|
||||||
var user = await _UserManager.FindByIdAsync(userId);
|
var user = await _UserManager.FindByIdAsync(userId);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
@@ -103,7 +108,8 @@ namespace BTCPayServer.Controllers
|
|||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
IdentityResult result;
|
IdentityResult result;
|
||||||
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, EmailConfirmed = model.EmailConfirmed, RequiresEmailConfirmation = _cssThemeManager.Policies.RequiresConfirmedEmail };
|
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, EmailConfirmed = model.EmailConfirmed, RequiresEmailConfirmation = _cssThemeManager.Policies.RequiresConfirmedEmail,
|
||||||
|
Created = DateTimeOffset.UtcNow };
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(model.Password))
|
if (!string.IsNullOrEmpty(model.Password))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Reflection;
|
|||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Models.InvoicingModels;
|
using BTCPayServer.Models.InvoicingModels;
|
||||||
using BTCPayServer.Models.PaymentRequestViewModels;
|
using BTCPayServer.Models.PaymentRequestViewModels;
|
||||||
|
using BTCPayServer.Models.ServerViewModels;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
@@ -18,6 +19,8 @@ namespace BTCPayServer
|
|||||||
prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.InvoicesQuery));
|
prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.InvoicesQuery));
|
||||||
else if (model is ListPaymentRequestsViewModel)
|
else if (model is ListPaymentRequestsViewModel)
|
||||||
prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.PaymentRequestsQuery));
|
prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.PaymentRequestsQuery));
|
||||||
|
else if (model is UsersViewModel)
|
||||||
|
prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.UsersQuery));
|
||||||
else
|
else
|
||||||
throw new Exception("Unsupported BasePagingViewModel for cookie user preferences saving");
|
throw new Exception("Unsupported BasePagingViewModel for cookie user preferences saving");
|
||||||
|
|
||||||
@@ -74,6 +77,7 @@ namespace BTCPayServer
|
|||||||
public ListQueryDataHolder InvoicesQuery { get; set; }
|
public ListQueryDataHolder InvoicesQuery { get; set; }
|
||||||
|
|
||||||
public ListQueryDataHolder PaymentRequestsQuery { get; set; }
|
public ListQueryDataHolder PaymentRequestsQuery { get; set; }
|
||||||
|
public ListQueryDataHolder UsersQuery { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class ListQueryDataHolder
|
class ListQueryDataHolder
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Models.ServerViewModels
|
|
||||||
{
|
|
||||||
public class UserViewModel
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
public string Email { get; set; }
|
|
||||||
[Display(Name = "Is admin")]
|
|
||||||
public bool IsAdmin { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace BTCPayServer.Models.ServerViewModels
|
namespace BTCPayServer.Models.ServerViewModels
|
||||||
{
|
{
|
||||||
public class UsersViewModel
|
public class UsersViewModel: BasePagingViewModel
|
||||||
{
|
{
|
||||||
public class UserViewModel
|
public class UserViewModel
|
||||||
{
|
{
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
|
public bool Verified { get; set; }
|
||||||
|
public bool IsAdmin { get; set; }
|
||||||
|
public DateTimeOffset? Created { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Skip { get; set; }
|
|
||||||
public int Count { get; set; }
|
|
||||||
public int Total { get; set; }
|
|
||||||
|
|
||||||
public List<UserViewModel> Users { get; set; } = new List<UserViewModel>();
|
public List<UserViewModel> Users { get; set; } = new List<UserViewModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,17 @@
|
|||||||
ViewData.SetActivePageAndTitle(ServerNavPages.Users);
|
ViewData.SetActivePageAndTitle(ServerNavPages.Users);
|
||||||
}
|
}
|
||||||
|
|
||||||
<partial name="_StatusMessage" />
|
<partial name="_StatusMessage"/>
|
||||||
|
|
||||||
<div class="row button-row">
|
<div class="row button-row">
|
||||||
<div class="col-lg-9 col-xl-8">
|
<div class="col-12 col-sm-4 col-lg-6 mb-3">
|
||||||
<span>Total Users: @Model.Total</span>
|
<a asp-action="CreateUser" class="btn btn-primary" role="button" id="CreateUser">
|
||||||
<span class="pull-right">
|
<span class="fa fa-plus"></span> Add User
|
||||||
<a asp-action="CreateUser" class="btn btn-primary" role="button" id="CreateUser">
|
</a>
|
||||||
<span class="fa fa-plus"></span> Add User
|
</div>
|
||||||
</a>
|
|
||||||
</span>
|
<div class="col-12 col-sm-8 col-lg-6 mb-3">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -22,6 +23,8 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Verified</th>
|
||||||
<th class="text-right">Actions</th>
|
<th class="text-right">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -30,63 +33,26 @@
|
|||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@user.Email</td>
|
<td>@user.Email</td>
|
||||||
<td class="text-right"><a asp-action="User" asp-route-userId="@user.Id">Edit</a> <span> - </span> <a asp-action="DeleteUser" asp-route-userId="@user.Id">Remove</a></td>
|
<td>@user.Created?.ToBrowserDate()</td>
|
||||||
|
<td class="text-center">
|
||||||
|
@if (user.Verified)
|
||||||
|
{
|
||||||
|
<span class="text-success fa fa-check"></span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-danger fa fa-times"></span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<a asp-action="User" asp-route-userId="@user.Id">Edit</a> <span> - </span> <a asp-action="DeleteUser" asp-route-userId="@user.Id">Remove</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<nav aria-label="..." class="w-100">
|
|
||||||
<ul class="pagination float-left">
|
<vc:pager view-model="Model"></vc:pager>
|
||||||
<li class="page-item @(Model.Skip == 0 ? "disabled" : null)">
|
|
||||||
<a class="page-link" tabindex="-1" href="@listUsers(-1, Model.Count)">«</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link">@(Model.Skip + 1) to @(Model.Skip + Model.Users.Count) of @Model.Total</span>
|
|
||||||
</li>
|
|
||||||
<li class="page-item @(Model.Total > (Model.Skip + Model.Users.Count) ? null : "disabled")">
|
|
||||||
<a class="page-link" href="@listUsers(1, Model.Count)">»</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul class="pagination float-right">
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link">Page Size:</span>
|
|
||||||
</li>
|
|
||||||
<li class="page-item @(Model.Count == 50 ? "active" : null)">
|
|
||||||
<a class="page-link" href="@listUsers(0, 50)">50</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item @(Model.Count == 100 ? "active" : null)">
|
|
||||||
<a class="page-link" href="@listUsers(0, 100)">100</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item @(Model.Count == 250 ? "active" : null)">
|
|
||||||
<a class="page-link" href="@listUsers(0, 250)">250</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item @(Model.Count == 500 ? "active" : null)">
|
|
||||||
<a class="page-link" href="@listUsers(0, 500)">500</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
@{
|
|
||||||
string listUsers(int prevNext, int count)
|
|
||||||
{
|
|
||||||
var skip = Model.Skip;
|
|
||||||
if (prevNext == -1)
|
|
||||||
{
|
|
||||||
skip = Math.Max(0, Model.Skip - Model.Count);
|
|
||||||
}
|
|
||||||
else if (prevNext == 1)
|
|
||||||
{
|
|
||||||
skip = Model.Skip + count;
|
|
||||||
}
|
|
||||||
|
|
||||||
var act = Url.Action("ListUsers", new
|
|
||||||
{
|
|
||||||
skip = skip,
|
|
||||||
count = count,
|
|
||||||
});
|
|
||||||
|
|
||||||
return act;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -133,6 +133,10 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "True if the email requires email confirmation to log in"
|
"description": "True if the email requires email confirmation to log in"
|
||||||
},
|
},
|
||||||
|
"created": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The creation date of the user as a unix timestamp. Null if created before v1.0.5.6"
|
||||||
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
|
|||||||
Reference in New Issue
Block a user