mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Greenfield API: Create User
Slightly big PR because I started refactoring to reduce code duplication between the UI based business logic and the api one.
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
@@ -11,5 +12,12 @@ namespace BTCPayServer.Client
|
|||||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users/me"), token);
|
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users/me"), token);
|
||||||
return await HandleResponse<ApplicationUserData>(response);
|
return await HandleResponse<ApplicationUserData>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual async Task<ApplicationUserData> CreateUser(CreateApplicationUserRequest request,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users", null, request, HttpMethod.Post), token);
|
||||||
|
return await HandleResponse<ApplicationUserData>(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ namespace BTCPayServer.Client
|
|||||||
var request = CreateHttpRequest(path, queryPayload, method);
|
var request = CreateHttpRequest(path, queryPayload, method);
|
||||||
if (typeof(T).IsPrimitive || !EqualityComparer<T>.Default.Equals(bodyPayload, default(T)))
|
if (typeof(T).IsPrimitive || !EqualityComparer<T>.Default.Equals(bodyPayload, default(T)))
|
||||||
{
|
{
|
||||||
request.Content = new StringContent(JsonSerializer.Serialize(bodyPayload, _serializerOptions));
|
request.Content = new StringContent(JsonSerializer.Serialize(bodyPayload, _serializerOptions), Encoding.UTF8, "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
|
|||||||
@@ -2,7 +2,41 @@ namespace BTCPayServer.Client.Models
|
|||||||
{
|
{
|
||||||
public class ApplicationUserData
|
public class ApplicationUserData
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// the id of the user
|
||||||
|
/// </summary>
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// the email AND username of the user
|
||||||
|
/// </summary>
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the user has verified their email
|
||||||
|
/// </summary>
|
||||||
|
public bool EmailConfirmed { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// whether the user needed to verify their email on account creation
|
||||||
|
/// </summary>
|
||||||
|
public bool RequiresEmailConfirmation { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreateApplicationUserRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// the email AND username of the new user
|
||||||
|
/// </summary>
|
||||||
|
public string Email { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// password of the new user
|
||||||
|
/// </summary>
|
||||||
|
public string Password { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this user is an administrator. If left null and there are no admins in the system, the user will be created as an admin.
|
||||||
|
/// </summary>
|
||||||
|
public bool? IsAdministrator { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// If the server requires email confirmation, this allows you to set the account as confirmed from the start
|
||||||
|
/// </summary>
|
||||||
|
public bool? EmailConfirmed { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
|
using BTCPayServer.Controllers.RestApi.Users;
|
||||||
using BTCPayServer.Tests.Logging;
|
using BTCPayServer.Tests.Logging;
|
||||||
using Microsoft.AspNet.SignalR.Client;
|
using Microsoft.AspNet.SignalR.Client;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
@@ -72,6 +76,36 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientInsufficient.GetCurrentUser());
|
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientInsufficient.GetCurrentUser());
|
||||||
await clientServer.GetCurrentUser();
|
await clientServer.GetCurrentUser();
|
||||||
|
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientInsufficient.CreateUser(new CreateApplicationUserRequest()
|
||||||
|
{
|
||||||
|
Email = $"{Guid.NewGuid()}@g.com",
|
||||||
|
Password = Guid.NewGuid().ToString()
|
||||||
|
}) );
|
||||||
|
|
||||||
|
var newUser = await clientServer.CreateUser(new CreateApplicationUserRequest()
|
||||||
|
{
|
||||||
|
Email = $"{Guid.NewGuid()}@g.com", Password = Guid.NewGuid().ToString()
|
||||||
|
});
|
||||||
|
Assert.NotNull(newUser);
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientServer.CreateUser(new CreateApplicationUserRequest()
|
||||||
|
{
|
||||||
|
Email = $"{Guid.NewGuid()}",
|
||||||
|
Password = Guid.NewGuid().ToString()
|
||||||
|
}) );
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientServer.CreateUser(new CreateApplicationUserRequest()
|
||||||
|
{
|
||||||
|
Email = $"{Guid.NewGuid()}@g.com",
|
||||||
|
}) );
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<HttpRequestException>(async () => await clientServer.CreateUser(new CreateApplicationUserRequest()
|
||||||
|
{
|
||||||
|
Password = Guid.NewGuid().ToString()
|
||||||
|
}) );
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ using BTCPayServer.U2F.Models;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NicolasDorier.RateLimits;
|
using NicolasDorier.RateLimits;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Events;
|
||||||
using U2F.Core.Exceptions;
|
using U2F.Core.Exceptions;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
@@ -40,6 +41,7 @@ namespace BTCPayServer.Controllers
|
|||||||
Configuration.BTCPayServerOptions _Options;
|
Configuration.BTCPayServerOptions _Options;
|
||||||
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
|
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
|
||||||
public U2FService _u2FService;
|
public U2FService _u2FService;
|
||||||
|
private readonly EventAggregator _eventAggregator;
|
||||||
ILogger _logger;
|
ILogger _logger;
|
||||||
|
|
||||||
public AccountController(
|
public AccountController(
|
||||||
@@ -51,7 +53,8 @@ namespace BTCPayServer.Controllers
|
|||||||
SettingsRepository settingsRepository,
|
SettingsRepository settingsRepository,
|
||||||
Configuration.BTCPayServerOptions options,
|
Configuration.BTCPayServerOptions options,
|
||||||
BTCPayServerEnvironment btcPayServerEnvironment,
|
BTCPayServerEnvironment btcPayServerEnvironment,
|
||||||
U2FService u2FService)
|
U2FService u2FService,
|
||||||
|
EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
this.storeRepository = storeRepository;
|
this.storeRepository = storeRepository;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
@@ -62,6 +65,7 @@ namespace BTCPayServer.Controllers
|
|||||||
_Options = options;
|
_Options = options;
|
||||||
_btcPayServerEnvironment = btcPayServerEnvironment;
|
_btcPayServerEnvironment = btcPayServerEnvironment;
|
||||||
_u2FService = u2FService;
|
_u2FService = u2FService;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
_logger = Logs.PayServer;
|
_logger = Logs.PayServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,7 +443,6 @@ namespace BTCPayServer.Controllers
|
|||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
var admin = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
||||||
Logs.PayServer.LogInformation($"A new user just registered {user.Email} {(admin.Count == 0 ? "(admin)" : "")}");
|
|
||||||
if (admin.Count == 0 || (model.IsAdmin && _Options.AllowAdminRegistration))
|
if (admin.Count == 0 || (model.IsAdmin && _Options.AllowAdminRegistration))
|
||||||
{
|
{
|
||||||
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
|
await _RoleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
|
||||||
@@ -456,11 +459,14 @@ namespace BTCPayServer.Controllers
|
|||||||
RegisteredAdmin = true;
|
RegisteredAdmin = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
_eventAggregator.Publish(new UserRegisteredEvent()
|
||||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
{
|
||||||
|
Request = Request,
|
||||||
|
User = user,
|
||||||
|
Admin = RegisteredAdmin
|
||||||
|
});
|
||||||
RegisteredUserId = user.Id;
|
RegisteredUserId = user.Id;
|
||||||
|
|
||||||
_EmailSenderFactory.GetEmailSender().SendEmailConfirmation(model.Email, callbackUrl);
|
|
||||||
if (!policies.RequiresConfirmedEmail)
|
if (!policies.RequiresConfirmedEmail)
|
||||||
{
|
{
|
||||||
if (logon)
|
if (logon)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ using BTCPayServer.Security;
|
|||||||
using BTCPayServer.U2F;
|
using BTCPayServer.U2F;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Security.APIKeys;
|
using BTCPayServer.Security.APIKeys;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
@@ -38,6 +39,7 @@ namespace BTCPayServer.Controllers
|
|||||||
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
|
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
|
||||||
private readonly APIKeyRepository _apiKeyRepository;
|
private readonly APIKeyRepository _apiKeyRepository;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
private readonly LinkGenerator _linkGenerator;
|
||||||
StoreRepository _StoreRepository;
|
StoreRepository _StoreRepository;
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +56,8 @@ namespace BTCPayServer.Controllers
|
|||||||
U2FService u2FService,
|
U2FService u2FService,
|
||||||
BTCPayServerEnvironment btcPayServerEnvironment,
|
BTCPayServerEnvironment btcPayServerEnvironment,
|
||||||
APIKeyRepository apiKeyRepository,
|
APIKeyRepository apiKeyRepository,
|
||||||
IAuthorizationService authorizationService
|
IAuthorizationService authorizationService,
|
||||||
|
LinkGenerator linkGenerator
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
@@ -67,6 +70,7 @@ namespace BTCPayServer.Controllers
|
|||||||
_btcPayServerEnvironment = btcPayServerEnvironment;
|
_btcPayServerEnvironment = btcPayServerEnvironment;
|
||||||
_apiKeyRepository = apiKeyRepository;
|
_apiKeyRepository = apiKeyRepository;
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
|
_linkGenerator = linkGenerator;
|
||||||
_StoreRepository = storeRepository;
|
_StoreRepository = storeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +160,7 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
var callbackUrl = _linkGenerator.EmailConfirmationLink(user.Id, code, Request.Scheme, Request.HttpContext);
|
||||||
var email = user.Email;
|
var email = user.Email;
|
||||||
_EmailSenderFactory.GetEmailSender().SendEmailConfirmation(email, callbackUrl);
|
_EmailSenderFactory.GetEmailSender().SendEmailConfirmation(email, callbackUrl);
|
||||||
TempData[WellKnownTempData.SuccessMessage] = "Verification email sent. Please check your email.";
|
TempData[WellKnownTempData.SuccessMessage] = "Verification email sent. Please check your email.";
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Configuration;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Hosting.OpenApi;
|
using BTCPayServer.Hosting.OpenApi;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
|
using BTCPayServer.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using NSwag.Annotations;
|
using NSwag.Annotations;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers.RestApi.Users
|
namespace BTCPayServer.Controllers.RestApi.Users
|
||||||
@@ -18,14 +24,24 @@ namespace BTCPayServer.Controllers.RestApi.Users
|
|||||||
public class UsersController : ControllerBase
|
public class UsersController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly BTCPayServerOptions _btcPayServerOptions;
|
||||||
|
private readonly RoleManager<IdentityRole> _roleManager;
|
||||||
|
private readonly SettingsRepository _settingsRepository;
|
||||||
|
private readonly EventAggregator _eventAggregator;
|
||||||
|
|
||||||
public UsersController(UserManager<ApplicationUser> userManager)
|
public UsersController(UserManager<ApplicationUser> userManager, BTCPayServerOptions btcPayServerOptions,
|
||||||
|
RoleManager<IdentityRole> roleManager, SettingsRepository settingsRepository,
|
||||||
|
EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_btcPayServerOptions = btcPayServerOptions;
|
||||||
|
_roleManager = roleManager;
|
||||||
|
_settingsRepository = settingsRepository;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
}
|
}
|
||||||
|
|
||||||
[OpenApiOperation("Get current user information", "View information about the current user")]
|
[OpenApiOperation("Get current user information", "View information about the current user")]
|
||||||
[SwaggerResponse(StatusCodes.Status200OK, typeof(ApiKeyData),
|
[SwaggerResponse(StatusCodes.Status200OK, typeof(ApplicationUserData),
|
||||||
Description = "Information about the current user")]
|
Description = "Information about the current user")]
|
||||||
[Authorize(Policy = Policies.CanModifyProfile.Key, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
[Authorize(Policy = Policies.CanModifyProfile.Key, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
[HttpGet("~/api/v1/users/me")]
|
[HttpGet("~/api/v1/users/me")]
|
||||||
@@ -35,13 +51,81 @@ namespace BTCPayServer.Controllers.RestApi.Users
|
|||||||
return FromModel(user);
|
return FromModel(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[OpenApiOperation("Create user", "Create a new user")]
|
||||||
|
[SwaggerResponse(StatusCodes.Status201Created, typeof(ApplicationUserData),
|
||||||
|
Description = "Information about the new user")]
|
||||||
|
[SwaggerResponse(StatusCodes.Status422UnprocessableEntity, typeof(ValidationProblemDetails),
|
||||||
|
Description = "A list of validation errors that occurred")]
|
||||||
|
[SwaggerResponse(StatusCodes.Status400BadRequest, typeof(ValidationProblemDetails),
|
||||||
|
Description = "A list of errors that occurred when creating the user")]
|
||||||
|
[Authorize(Policy = Policies.CanCreateUser.Key, AuthenticationSchemes = AuthenticationSchemes.ApiKey)]
|
||||||
|
[HttpPost("~/api/v1/users")]
|
||||||
|
public async Task<ActionResult<ApplicationUserData>> CreateUser(CreateApplicationUserRequest request)
|
||||||
|
{
|
||||||
|
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||||
|
var anyAdmin = (await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin)).Any();
|
||||||
|
var admin = request.IsAdministrator.GetValueOrDefault(!anyAdmin);
|
||||||
|
var user = new ApplicationUser
|
||||||
|
{
|
||||||
|
UserName = request.Email,
|
||||||
|
Email = request.Email,
|
||||||
|
RequiresEmailConfirmation = policies.RequiresConfirmedEmail,
|
||||||
|
EmailConfirmed = request.EmailConfirmed.GetValueOrDefault(false)
|
||||||
|
};
|
||||||
|
var identityResult = await _userManager.CreateAsync(user);
|
||||||
|
if (!identityResult.Succeeded)
|
||||||
|
{
|
||||||
|
AddErrors(identityResult);
|
||||||
|
return BadRequest(new ValidationProblemDetails(ModelState));
|
||||||
|
}
|
||||||
|
else if (admin)
|
||||||
|
{
|
||||||
|
await _roleManager.CreateAsync(new IdentityRole(Roles.ServerAdmin));
|
||||||
|
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
||||||
|
}
|
||||||
|
|
||||||
|
_eventAggregator.Publish(new UserRegisteredEvent() {Request = Request, User = user, Admin = admin});
|
||||||
|
|
||||||
|
return CreatedAtAction("", user);
|
||||||
|
}
|
||||||
|
|
||||||
private static ApplicationUserData FromModel(ApplicationUser data)
|
private static ApplicationUserData FromModel(ApplicationUser data)
|
||||||
{
|
{
|
||||||
return new ApplicationUserData()
|
return new ApplicationUserData()
|
||||||
{
|
{
|
||||||
Id = data.Id,
|
Id = data.Id,
|
||||||
Email = data.Email
|
Email = data.Email,
|
||||||
|
EmailConfirmed = data.EmailConfirmed,
|
||||||
|
RequiresEmailConfirmation = data.RequiresEmailConfirmation
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddErrors(IdentityResult result)
|
||||||
|
{
|
||||||
|
foreach (var error in result.Errors)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(string.Empty, error.Description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ModelMetadataType(typeof(CreateApplicationUserRequestMetadata))]
|
||||||
|
public class CreateApplicationUserRequest : BTCPayServer.Client.Models.CreateApplicationUserRequest
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreateApplicationUserRequestMetadata
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[EmailAddress]
|
||||||
|
[Display(Name = "Email")]
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
[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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
BTCPayServer/Events/UserRegisteredEvent.cs
Normal file
12
BTCPayServer/Events/UserRegisteredEvent.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using BTCPayServer.Data;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Events
|
||||||
|
{
|
||||||
|
public class UserRegisteredEvent
|
||||||
|
{
|
||||||
|
public ApplicationUser User { get; set; }
|
||||||
|
public HttpRequest Request { get; set; }
|
||||||
|
public bool Admin { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
public static class UrlHelperExtensions
|
public static class UrlHelperExtensions
|
||||||
{
|
{
|
||||||
public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
|
public static string EmailConfirmationLink(this LinkGenerator urlHelper, string userId, string code, string scheme, HttpContext context)
|
||||||
{
|
{
|
||||||
return urlHelper.Action(
|
return urlHelper.GetUriByAction(context, nameof(AccountController.ConfirmEmail), "Account",
|
||||||
action: nameof(AccountController.ConfirmEmail),
|
new {userId, code}, scheme);
|
||||||
controller: "Account",
|
|
||||||
values: new { userId, code },
|
|
||||||
protocol: scheme);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
|
public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
|
||||||
|
|||||||
53
BTCPayServer/HostedServices/UserEventHostedService.cs
Normal file
53
BTCPayServer/HostedServices/UserEventHostedService.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Events;
|
||||||
|
using BTCPayServer.Logging;
|
||||||
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Mails;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace BTCPayServer.HostedServices
|
||||||
|
{
|
||||||
|
public class UserEventHostedService : EventHostedServiceBase
|
||||||
|
{
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly EmailSenderFactory _emailSenderFactory;
|
||||||
|
private readonly LinkGenerator _generator;
|
||||||
|
|
||||||
|
public UserEventHostedService(EventAggregator eventAggregator, UserManager<ApplicationUser> userManager,
|
||||||
|
EmailSenderFactory emailSenderFactory, LinkGenerator generator) : base(eventAggregator)
|
||||||
|
{
|
||||||
|
_userManager = userManager;
|
||||||
|
_emailSenderFactory = emailSenderFactory;
|
||||||
|
_generator = generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SubscibeToEvents()
|
||||||
|
{
|
||||||
|
Subscribe<UserRegisteredEvent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
switch (evt)
|
||||||
|
{
|
||||||
|
case UserRegisteredEvent userRegisteredEvent:
|
||||||
|
Logs.PayServer.LogInformation($"A new user just registered {userRegisteredEvent.User.Email} {(userRegisteredEvent.Admin ? "(admin)" : "")}");
|
||||||
|
if (!userRegisteredEvent.User.EmailConfirmed && userRegisteredEvent.User.RequiresEmailConfirmation)
|
||||||
|
{
|
||||||
|
var code = await _userManager.GenerateEmailConfirmationTokenAsync(userRegisteredEvent.User);
|
||||||
|
var callbackUrl = _generator.EmailConfirmationLink(userRegisteredEvent.User.Id, code, userRegisteredEvent.Request.Scheme, userRegisteredEvent.Request.HttpContext);
|
||||||
|
|
||||||
|
_emailSenderFactory.GetEmailSender()
|
||||||
|
.SendEmailConfirmation(userRegisteredEvent.User.Email, callbackUrl);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -204,6 +204,7 @@ namespace BTCPayServer.Hosting
|
|||||||
services.AddSingleton<IHostedService, BackgroundJobSchedulerHostedService>();
|
services.AddSingleton<IHostedService, BackgroundJobSchedulerHostedService>();
|
||||||
services.AddSingleton<IHostedService, AppHubStreamer>();
|
services.AddSingleton<IHostedService, AppHubStreamer>();
|
||||||
services.AddSingleton<IHostedService, AppInventoryUpdaterHostedService>();
|
services.AddSingleton<IHostedService, AppInventoryUpdaterHostedService>();
|
||||||
|
services.AddSingleton<IHostedService, UserEventHostedService>();
|
||||||
services.AddSingleton<IHostedService, DynamicDnsHostedService>();
|
services.AddSingleton<IHostedService, DynamicDnsHostedService>();
|
||||||
services.AddSingleton<IHostedService, TorServicesHostedService>();
|
services.AddSingleton<IHostedService, TorServicesHostedService>();
|
||||||
services.AddSingleton<IHostedService, PaymentRequestStreamer>();
|
services.AddSingleton<IHostedService, PaymentRequestStreamer>();
|
||||||
|
|||||||
@@ -72,6 +72,16 @@ namespace BTCPayServer.Hosting
|
|||||||
// ScriptSrc = "'self' 'unsafe-inline'"
|
// ScriptSrc = "'self' 'unsafe-inline'"
|
||||||
//});
|
//});
|
||||||
})
|
})
|
||||||
|
.ConfigureApiBehaviorOptions(options =>
|
||||||
|
{
|
||||||
|
var builtInFactory = options.InvalidModelStateResponseFactory;
|
||||||
|
|
||||||
|
options.InvalidModelStateResponseFactory = context =>
|
||||||
|
{
|
||||||
|
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.UnprocessableEntity;
|
||||||
|
return builtInFactory(context);
|
||||||
|
};
|
||||||
|
})
|
||||||
.AddNewtonsoftJson()
|
.AddNewtonsoftJson()
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
.AddRazorRuntimeCompilation()
|
.AddRazorRuntimeCompilation()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ namespace BTCPayServer.Security.APIKeys
|
|||||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
||||||
PolicyRequirement requirement)
|
PolicyRequirement requirement)
|
||||||
{
|
{
|
||||||
|
//if it is a create user request, and the auth is not specified, and there are no admins in the system: authorize
|
||||||
|
if (context.User.Identity.AuthenticationType == null && requirement.Policy == Policies.CanCreateUser.Key &&
|
||||||
|
!(await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin)).Any())
|
||||||
|
{
|
||||||
|
context.Succeed(requirement);
|
||||||
|
}
|
||||||
|
|
||||||
if (context.User.Identity.AuthenticationType != APIKeyConstants.AuthenticationType)
|
if (context.User.Identity.AuthenticationType != APIKeyConstants.AuthenticationType)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -67,6 +74,7 @@ namespace BTCPayServer.Security.APIKeys
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case Policies.CanCreateUser.Key:
|
||||||
case Policies.CanModifyServerSettings.Key:
|
case Policies.CanModifyServerSettings.Key:
|
||||||
if (!context.HasPermissions(Permissions.ServerManagement))
|
if (!context.HasPermissions(Permissions.ServerManagement))
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace BTCPayServer.Security
|
|||||||
options.AddPolicy(CanModifyServerSettings.Key);
|
options.AddPolicy(CanModifyServerSettings.Key);
|
||||||
options.AddPolicy(CanModifyServerSettings.Key);
|
options.AddPolicy(CanModifyServerSettings.Key);
|
||||||
options.AddPolicy(CanModifyProfile.Key);
|
options.AddPolicy(CanModifyProfile.Key);
|
||||||
|
options.AddPolicy(CanCreateUser.Key);
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,5 +47,10 @@ namespace BTCPayServer.Security
|
|||||||
{
|
{
|
||||||
public const string Key = "btcpay.store.cangetrates";
|
public const string Key = "btcpay.store.cangetrates";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class CanCreateUser
|
||||||
|
{
|
||||||
|
public const string Key = "btcpay.store.cancreateuser";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
|
|||||||
Reference in New Issue
Block a user