btcpayserver/BTCPayServer/App/BtcPayAppController.cs

259 lines
10 KiB
C#
Raw Normal View History

2023-06-23 15:10:44 +02:00
#nullable enable
2024-02-20 11:47:03 +01:00
using System;
2023-06-23 15:10:44 +02:00
using System.Linq;
using System.Threading.Tasks;
2023-07-03 09:56:00 +02:00
using BTCPayApp.CommonServer;
2024-02-20 11:47:03 +01:00
using BTCPayServer.Abstractions.Constants;
2024-04-11 11:06:08 +02:00
using BTCPayServer.Abstractions.Contracts;
2024-02-20 11:47:03 +01:00
using BTCPayServer.Abstractions.Extensions;
2023-06-23 15:10:44 +02:00
using BTCPayServer.Client;
2024-02-20 11:47:03 +01:00
using BTCPayServer.Common;
using BTCPayServer.Controllers;
2023-06-23 15:10:44 +02:00
using BTCPayServer.Data;
2024-02-20 11:47:03 +01:00
using BTCPayServer.Events;
2023-06-23 15:10:44 +02:00
using BTCPayServer.Security.Greenfield;
2024-02-20 11:47:03 +01:00
using BTCPayServer.Services;
2024-04-04 11:24:56 +02:00
using BTCPayServer.Services.Invoices;
2023-06-23 15:10:44 +02:00
using BTCPayServer.Services.Stores;
2024-02-20 11:47:03 +01:00
using Microsoft.AspNetCore.Authentication.BearerToken;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.Data;
2023-06-23 15:10:44 +02:00
using Microsoft.AspNetCore.Mvc;
2024-02-20 11:47:03 +01:00
using Microsoft.Extensions.Options;
2023-06-23 15:10:44 +02:00
using NBitcoin;
using NBitcoin.DataEncoders;
using NBXplorer;
2024-02-20 11:47:03 +01:00
using NicolasDorier.RateLimits;
2023-06-23 15:10:44 +02:00
2024-02-20 11:47:03 +01:00
namespace BTCPayServer.App;
2023-06-23 15:10:44 +02:00
2024-02-20 11:47:03 +01:00
[ApiController]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Bearer)]
2023-06-23 15:10:44 +02:00
[Route("btcpayapp")]
2024-02-20 11:47:03 +01:00
public class BtcPayAppController(
APIKeyRepository apiKeyRepository,
StoreRepository storeRepository,
BTCPayNetworkProvider btcPayNetworkProvider,
IExplorerClientProvider explorerClientProvider,
EventAggregator eventAggregator,
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
TimeProvider timeProvider,
2024-04-04 11:24:56 +02:00
PaymentMethodHandlerDictionary handlers,
2024-04-11 11:06:08 +02:00
IFileService fileService,
ISettingsRepository settingsRepository,
2024-05-17 17:38:58 +02:00
UriResolver uriResolver,
2024-02-20 11:47:03 +01:00
IOptionsMonitor<BearerTokenOptions> bearerTokenOptions)
: Controller
2023-06-23 15:10:44 +02:00
{
2024-04-11 11:06:08 +02:00
[AllowAnonymous]
[HttpGet("instance")]
public async Task<Results<Ok<AppInstanceInfo>, NotFound>> Instance()
{
var serverSettings = await settingsRepository.GetSettingAsync<ServerSettings>() ?? new ServerSettings();
var policiesSettings = await settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
var themeSettings = await settingsRepository.GetSettingAsync<ThemeSettings>() ?? new ThemeSettings();
return TypedResults.Ok(new AppInstanceInfo
{
BaseUrl = Request.GetAbsoluteRoot(),
ServerName = serverSettings.ServerName,
ContactUrl = serverSettings.ContactUrl,
RegistrationEnabled = policiesSettings.EnableRegistration,
CustomThemeExtension = themeSettings.CustomTheme ? themeSettings.CustomThemeExtension.ToString() : null,
2024-05-13 12:04:23 +02:00
CustomThemeCssUrl = themeSettings.CustomTheme && !string.IsNullOrEmpty(themeSettings.CustomThemeCssUrl?.ToString())
2024-05-17 17:38:58 +02:00
? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), themeSettings.CustomThemeCssUrl)
2024-04-11 11:06:08 +02:00
: null,
2024-05-13 12:04:23 +02:00
LogoUrl = !string.IsNullOrEmpty(themeSettings.LogoUrl?.ToString())
2024-05-17 17:38:58 +02:00
? await uriResolver.Resolve(Request.GetAbsoluteRootUri(), themeSettings.LogoUrl)
2024-04-11 11:06:08 +02:00
: null
});
}
[AllowAnonymous]
[HttpPost("register")]
[RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)]
public async Task<Results<Ok<SignupResult>, ValidationProblem, ProblemHttpResult>> Register(SignupRequest signup)
{
var policiesSettings = await settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
if (policiesSettings.LockSubscription)
return TypedResults.Problem("This instance does not allow public user registration", statusCode: 406);
var errorMessage = "Invalid signup attempt.";
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = signup.Email,
Email = signup.Email,
RequiresEmailConfirmation = policiesSettings.RequiresConfirmedEmail,
RequiresApproval = policiesSettings.RequiresUserApproval,
Created = DateTimeOffset.UtcNow
};
var result = await userManager.CreateAsync(user, signup.Password);
if (result.Succeeded)
{
eventAggregator.Publish(new UserRegisteredEvent
{
RequestUri = Request.GetAbsoluteRootUri(),
User = user
});
var response = new SignupResult
{
Email = user.Email,
RequiresConfirmedEmail = policiesSettings.RequiresConfirmedEmail && !user.EmailConfirmed,
RequiresUserApproval = policiesSettings.RequiresUserApproval && !user.Approved
};
return TypedResults.Ok(response);
}
errorMessage = result.ToString();
}
return TypedResults.Problem(errorMessage, statusCode: 400);
}
2024-02-20 11:47:03 +01:00
[AllowAnonymous]
[HttpPost("login")]
[RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)]
public async Task<Results<Ok<AccessTokenResponse>, EmptyHttpResult, ProblemHttpResult>> Login(LoginRequest login)
2023-06-23 15:10:44 +02:00
{
2024-02-20 11:47:03 +01:00
const string errorMessage = "Invalid login attempt.";
if (ModelState.IsValid)
{
// Require the user to pass basic checks (approval, confirmed email, not disabled) before they can log on
var user = await userManager.FindByEmailAsync(login.Email);
if (!UserService.TryCanLogin(user, out var message))
{
return TypedResults.Problem(message, statusCode: 401);
}
signInManager.AuthenticationScheme = AuthenticationSchemes.Bearer;
var signInResult = await signInManager.PasswordSignInAsync(login.Email, login.Password, true, true);
if (signInResult.RequiresTwoFactor)
{
if (!string.IsNullOrEmpty(login.TwoFactorCode))
signInResult = await signInManager.TwoFactorAuthenticatorSignInAsync(login.TwoFactorCode, true, true);
else if (!string.IsNullOrEmpty(login.TwoFactorRecoveryCode))
signInResult = await signInManager.TwoFactorRecoveryCodeSignInAsync(login.TwoFactorRecoveryCode);
}
// TODO: Add FIDO and LNURL Auth
return signInResult.Succeeded
? TypedResults.Empty
: TypedResults.Problem(signInResult.ToString(), statusCode: 401);
}
return TypedResults.Problem(errorMessage, statusCode: 401);
}
[AllowAnonymous]
[HttpPost("refresh")]
[RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)]
public async Task<Results<Ok<AccessTokenResponse>, UnauthorizedHttpResult, SignInHttpResult, ChallengeHttpResult>> Refresh(RefreshRequest refresh)
{
const string scheme = AuthenticationSchemes.Bearer;
var authenticationTicket = bearerTokenOptions.Get(scheme).RefreshTokenProtector.Unprotect(refresh.RefreshToken);
var expiresUtc = authenticationTicket?.Properties.ExpiresUtc;
ApplicationUser? user = null;
int num;
if (expiresUtc.HasValue)
{
DateTimeOffset valueOrDefault = expiresUtc.GetValueOrDefault();
num = timeProvider.GetUtcNow() >= valueOrDefault ? 1 : 0;
}
else
num = 1;
bool flag = num != 0;
if (!flag)
{
signInManager.AuthenticationScheme = scheme;
user = await signInManager.ValidateSecurityStampAsync(authenticationTicket?.Principal);
}
return user != null
? TypedResults.SignIn(await signInManager.CreateUserPrincipalAsync(user), authenticationScheme: scheme)
: TypedResults.Challenge(authenticationSchemes: new[] { scheme });
}
[HttpPost("logout")]
public async Task<IResult> Logout()
{
var user = await userManager.GetUserAsync(User);
if (user != null)
{
await signInManager.SignOutAsync();
return Results.Ok();
}
return Results.Unauthorized();
}
2024-04-11 11:06:08 +02:00
[HttpGet("user")]
public async Task<Results<Ok<AppUserInfo>, NotFound>> UserInfo()
2024-02-20 11:47:03 +01:00
{
var user = await userManager.GetUserAsync(User);
if (user == null) return TypedResults.NotFound();
var userStores = await storeRepository.GetStoresByUserId(user.Id);
2024-04-09 20:45:21 +02:00
return TypedResults.Ok(new AppUserInfo
2024-02-20 11:47:03 +01:00
{
UserId = user.Id,
Email = await userManager.GetEmailAsync(user),
Roles = await userManager.GetRolesAsync(user),
Stores = (from store in userStores
let userStore = store.UserStores.Find(us => us.ApplicationUserId == user.Id && us.StoreDataId == store.Id)!
select new AppUserStoreInfo
{
Id = store.Id,
Name = store.StoreName,
Archived = store.Archived,
RoleId = userStore.StoreRole.Id,
Permissions = userStore.StoreRole.Permissions
}).ToList()
});
}
[AllowAnonymous]
[HttpPost("forgot-password")]
[RateLimitsFilter(ZoneLimits.ForgotPassword, Scope = RateLimitsScope.RemoteAddress)]
public async Task<IResult> ForgotPassword(ResetPasswordRequest resetRequest)
{
var user = await userManager.FindByEmailAsync(resetRequest.Email);
if (UserService.TryCanLogin(user, out _))
{
eventAggregator.Publish(new UserPasswordResetRequestedEvent
{
User = user,
RequestUri = Request.GetAbsoluteRootUri()
});
}
return TypedResults.Ok();
}
[AllowAnonymous]
[HttpPost("reset-password")]
public async Task<IResult> SetPassword(ResetPasswordRequest resetRequest)
{
var user = await userManager.FindByEmailAsync(resetRequest.Email);
if (!UserService.TryCanLogin(user, out _))
{
return TypedResults.Problem("Invalid account", statusCode: 401);
}
IdentityResult result;
try
{
result = await userManager.ResetPasswordAsync(user, resetRequest.ResetCode, resetRequest.NewPassword);
}
catch (FormatException)
{
result = IdentityResult.Failed(userManager.ErrorDescriber.InvalidToken());
}
return result.Succeeded ? TypedResults.Ok() : TypedResults.Problem(result.ToString().Split(": ").Last(), statusCode: 401);
2023-06-23 15:10:44 +02:00
}
2024-05-02 15:00:09 +02:00
2023-06-23 15:10:44 +02:00
}