mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-23 06:35:13 +01:00
Add login code support for the app
This commit is contained in:
parent
048d0c445f
commit
6bc5c12051
6 changed files with 83 additions and 23 deletions
|
@ -19,6 +19,7 @@ using Microsoft.AspNetCore.Http.HttpResults;
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NicolasDorier.RateLimits;
|
||||
using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType;
|
||||
|
||||
|
@ -74,7 +75,7 @@ public partial class AppApiController
|
|||
[RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)]
|
||||
public async Task<Results<Ok<AccessTokenResponse>, EmptyHttpResult, ProblemHttpResult>> Login(LoginRequest login)
|
||||
{
|
||||
const string errorMessage = "Invalid login attempt.";
|
||||
var errorMessage = "Invalid login attempt.";
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
// Require the user to pass basic checks (approval, confirmed email, not disabled) before they can log on
|
||||
|
@ -96,9 +97,43 @@ public partial class AppApiController
|
|||
|
||||
// TODO: Add FIDO and LNURL Auth
|
||||
|
||||
return signInResult.Succeeded
|
||||
? TypedResults.Empty
|
||||
: TypedResults.Problem(signInResult.ToString(), statusCode: 401);
|
||||
if (signInResult.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User {Email} tried to log in, but is locked out", user.Email);
|
||||
}
|
||||
else if (signInResult.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User {Email} logged in", user.Email);
|
||||
return TypedResults.Empty;
|
||||
}
|
||||
|
||||
errorMessage = signInResult.ToString();
|
||||
}
|
||||
|
||||
return TypedResults.Problem(errorMessage, statusCode: 401);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login/code")]
|
||||
[RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)]
|
||||
public async Task<Results<Ok<AccessTokenResponse>, EmptyHttpResult, ProblemHttpResult>> LoginWithCode([FromBody] string loginCode)
|
||||
{
|
||||
const string errorMessage = "Invalid login attempt.";
|
||||
if (!string.IsNullOrEmpty(loginCode))
|
||||
{
|
||||
var code = loginCode.Split(';').First();
|
||||
var userId = userLoginCodeService.Verify(code);
|
||||
var user = userId is null ? null : await userManager.FindByIdAsync(userId);
|
||||
if (!UserService.TryCanLogin(user, out var message))
|
||||
{
|
||||
return TypedResults.Problem(message, statusCode: 401);
|
||||
}
|
||||
|
||||
signInManager.AuthenticationScheme = AuthenticationSchemes.GreenfieldBearer;
|
||||
await signInManager.SignInAsync(user, false, "LoginCode");
|
||||
|
||||
_logger.LogInformation("User {Email} logged in with a login code", user.Email);
|
||||
return TypedResults.Empty;
|
||||
}
|
||||
|
||||
return TypedResults.Problem(errorMessage, statusCode: 401);
|
||||
|
@ -141,6 +176,7 @@ public partial class AppApiController
|
|||
if (user != null)
|
||||
{
|
||||
await signInManager.SignOutAsync();
|
||||
_logger.LogInformation("User {Email} logged out", user.Email);
|
||||
return Results.Ok();
|
||||
}
|
||||
return Results.Unauthorized();
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayApp.CommonServer;
|
||||
using BTCPayApp.CommonServer.Models;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Fido2;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Rates;
|
||||
|
@ -17,6 +18,8 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BTCPayServer.App.API;
|
||||
|
@ -35,9 +38,14 @@ public partial class AppApiController(
|
|||
UriResolver uriResolver,
|
||||
DefaultRulesCollection defaultRules,
|
||||
RateFetcher rateFactory,
|
||||
LinkGenerator linkGenerator,
|
||||
UserLoginCodeService userLoginCodeService,
|
||||
Logs logs,
|
||||
IOptionsMonitor<BearerTokenOptions> bearerTokenOptions)
|
||||
: Controller
|
||||
{
|
||||
private readonly ILogger _logger = logs.PayServer;
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("instance")]
|
||||
public async Task<Results<Ok<AppInstanceInfo>, NotFound>> Instance()
|
||||
|
|
|
@ -130,7 +130,8 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
if (!string.IsNullOrEmpty(loginCode))
|
||||
{
|
||||
var userId = _userLoginCodeService.Verify(loginCode);
|
||||
var code = loginCode.Split(';').First();
|
||||
var userId = _userLoginCodeService.Verify(code);
|
||||
if (userId is null)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Login code was invalid";
|
||||
|
|
|
@ -15,7 +15,9 @@ namespace BTCPayServer.Controllers
|
|||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
return View(nameof(LoginCodes), _userLoginCodeService.GetOrGenerate(user.Id));
|
||||
var indexUrl = _linkGenerator.IndexLink(Request.Scheme, Request.Host, Request.PathBase);
|
||||
var loginCode = _userLoginCodeService.GetOrGenerate(user.Id);
|
||||
return View(nameof(LoginCodes), $"{loginCode};{indexUrl};{user.Email}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,5 +109,14 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
values: new { storeId = wallet?.StoreId ?? walletIdOrStoreId, pullPaymentId, payoutState },
|
||||
scheme, host, pathbase);
|
||||
}
|
||||
|
||||
public static string IndexLink(this LinkGenerator urlHelper, string scheme, HostString host, string pathbase)
|
||||
{
|
||||
return urlHelper.GetUriByAction(
|
||||
action: nameof(UIHomeController.Index),
|
||||
controller: "UIHome",
|
||||
values: null,
|
||||
scheme, host, pathbase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,22 +22,26 @@
|
|||
|
||||
@section PageFootContent
|
||||
{
|
||||
<link href="~/main/qrcode.css" rel="stylesheet" asp-append-version="true"/>
|
||||
<link href="~/main/qrcode.css" rel="stylesheet" asp-append-version="true" />
|
||||
<script src="~/js/copy-to-clipboard.js"></script>
|
||||
<script>
|
||||
const SECONDS = 60
|
||||
const progress = document.getElementById('progress')
|
||||
const progressbar = document.getElementById('progressbar')
|
||||
let remaining = SECONDS
|
||||
const update = () => {
|
||||
remaining--
|
||||
const percent = Math.round(remaining/SECONDS * 100)
|
||||
progress.innerText = `Valid for ${remaining} seconds`
|
||||
progressbar.style.width = `${percent}%`
|
||||
if (percent < 15) progressbar.classList.add('bg-warning')
|
||||
if (percent < 1) document.getElementById('regeneratecode').click()
|
||||
}
|
||||
setInterval(update, 1000)
|
||||
update()
|
||||
(function () {
|
||||
const SECONDS = 60
|
||||
const progress = document.getElementById('progress')
|
||||
const progressbar = document.getElementById('progressbar')
|
||||
let remaining = SECONDS
|
||||
let handle = setInterval(update, 1000)
|
||||
function update() {
|
||||
remaining--
|
||||
const percent = Math.round(remaining/SECONDS * 100)
|
||||
progress.innerText = `Valid for ${remaining} seconds`
|
||||
progressbar.style.width = `${percent}%`
|
||||
if (percent < 15) progressbar.classList.add('bg-warning')
|
||||
if (percent < 1) {
|
||||
clearInterval(handle)
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue