mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-23 22:46:49 +01:00
switch to a long for device identifier
This commit is contained in:
parent
c0ad32eaf7
commit
7bdb136c27
4 changed files with 70 additions and 55 deletions
|
@ -27,7 +27,7 @@ public interface IBTCPayAppHubClient
|
||||||
//methods available on the hub in the server
|
//methods available on the hub in the server
|
||||||
public interface IBTCPayAppHubServer
|
public interface IBTCPayAppHubServer
|
||||||
{
|
{
|
||||||
Task<bool> DeviceMasterSignal(string deviceIdentifier, bool active);
|
Task<bool> DeviceMasterSignal(long deviceIdentifier, bool active);
|
||||||
|
|
||||||
Task<Dictionary<string,string>> Pair(PairRequest request);
|
Task<Dictionary<string,string>> Pair(PairRequest request);
|
||||||
Task<AppHandshakeResponse> Handshake(AppHandshake request);
|
Task<AppHandshakeResponse> Handshake(AppHandshake request);
|
||||||
|
|
|
@ -15,6 +15,8 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using VSSProto;
|
using VSSProto;
|
||||||
|
|
||||||
namespace BTCPayServer.App.API;
|
namespace BTCPayServer.App.API;
|
||||||
|
@ -29,20 +31,21 @@ public class VSSController : Controller, IVSSAPI
|
||||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly BTCPayAppState _appState;
|
private readonly BTCPayAppState _appState;
|
||||||
|
private readonly ILogger<VSSController> _logger;
|
||||||
|
|
||||||
public VSSController(ApplicationDbContextFactory dbContextFactory,
|
public VSSController(ApplicationDbContextFactory dbContextFactory,
|
||||||
UserManager<ApplicationUser> userManager, BTCPayAppState appState)
|
UserManager<ApplicationUser> userManager, BTCPayAppState appState, ILogger<VSSController> logger)
|
||||||
{
|
{
|
||||||
_dbContextFactory = dbContextFactory;
|
_dbContextFactory = dbContextFactory;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_appState = appState;
|
_appState = appState;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(HttpVSSAPIClient.GET_OBJECT)]
|
[HttpPost(HttpVSSAPIClient.GET_OBJECT)]
|
||||||
[MediaTypeConstraint("application/octet-stream")]
|
[MediaTypeConstraint("application/octet-stream")]
|
||||||
public async Task<GetObjectResponse> GetObjectAsync(GetObjectRequest request, CancellationToken cancellationToken)
|
public async Task<GetObjectResponse> GetObjectAsync(GetObjectRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|
||||||
var userId = _userManager.GetUserId(User);
|
var userId = _userManager.GetUserId(User);
|
||||||
await using var dbContext = _dbContextFactory.CreateContext();
|
await using var dbContext = _dbContextFactory.CreateContext();
|
||||||
var store = await dbContext.AppStorageItems.SingleOrDefaultAsync(data =>
|
var store = await dbContext.AppStorageItems.SingleOrDefaultAsync(data =>
|
||||||
|
@ -74,22 +77,20 @@ public class VSSController : Controller, IVSSAPI
|
||||||
|
|
||||||
private bool VerifyGlobalVersion(long globalVersion)
|
private bool VerifyGlobalVersion(long globalVersion)
|
||||||
{
|
{
|
||||||
|
|
||||||
var userId = _userManager.GetUserId(User);
|
var userId = _userManager.GetUserId(User);
|
||||||
var conn = _appState.Connections.SingleOrDefault(pair => pair.Value.Master && pair.Value.UserId == userId);
|
var conn = _appState.Connections.SingleOrDefault(pair => pair.Value.Master && pair.Value.UserId == userId);
|
||||||
if (conn.Key == null)
|
if (conn.Key == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// This has a high collision rate, but we're not expecting something insane here since we have auth and other checks in place.
|
|
||||||
return globalVersion == conn.Value.DeviceIdentifier.GetHashCode();
|
return globalVersion == conn.Value.DeviceIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(HttpVSSAPIClient.PUT_OBJECTS)]
|
[HttpPost(HttpVSSAPIClient.PUT_OBJECTS)]
|
||||||
[MediaTypeConstraint("application/octet-stream")]
|
[MediaTypeConstraint("application/octet-stream")]
|
||||||
public async Task<PutObjectResponse> PutObjectAsync(PutObjectRequest request, CancellationToken cancellationToken)
|
public async Task<PutObjectResponse> PutObjectAsync(PutObjectRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!VerifyGlobalVersion(request.GlobalVersion))
|
if (!VerifyGlobalVersion(request.GlobalVersion))
|
||||||
return SetResult<PutObjectResponse>(BadRequest(new ErrorResponse()
|
return SetResult<PutObjectResponse>(BadRequest(new ErrorResponse()
|
||||||
{
|
{
|
||||||
|
@ -99,7 +100,8 @@ public class VSSController : Controller, IVSSAPI
|
||||||
var userId = _userManager.GetUserId(User);
|
var userId = _userManager.GetUserId(User);
|
||||||
|
|
||||||
await using var dbContext = _dbContextFactory.CreateContext();
|
await using var dbContext = _dbContextFactory.CreateContext();
|
||||||
|
return await dbContext.Database.CreateExecutionStrategy().ExecuteAsync(async async =>
|
||||||
|
{
|
||||||
await using var dbContextTransaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
|
await using var dbContextTransaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -122,31 +124,32 @@ public class VSSController : Controller, IVSSAPI
|
||||||
|
|
||||||
await dbContext.SaveChangesAsync(cancellationToken);
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
await dbContextTransaction.CommitAsync(cancellationToken);
|
await dbContextTransaction.CommitAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new PutObjectResponse();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
_logger.LogError(e, "Error while processing transaction");
|
||||||
await dbContextTransaction.RollbackAsync(cancellationToken);
|
await dbContextTransaction.RollbackAsync(cancellationToken);
|
||||||
return SetResult<PutObjectResponse>(BadRequest(new ErrorResponse()
|
return SetResult<PutObjectResponse>(BadRequest(new ErrorResponse()
|
||||||
{
|
{
|
||||||
ErrorCode = ErrorCode.ConflictException, Message = e.Message
|
ErrorCode = ErrorCode.ConflictException, Message = e.Message
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
}, cancellationToken);
|
||||||
|
|
||||||
return new PutObjectResponse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(HttpVSSAPIClient.DELETE_OBJECT)]
|
[HttpPost(HttpVSSAPIClient.DELETE_OBJECT)]
|
||||||
[MediaTypeConstraint("application/octet-stream")]
|
[MediaTypeConstraint("application/octet-stream")]
|
||||||
public async Task<DeleteObjectResponse> DeleteObjectAsync(DeleteObjectRequest request, CancellationToken cancellationToken)
|
public async Task<DeleteObjectResponse> DeleteObjectAsync(DeleteObjectRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
var userId = _userManager.GetUserId(User);
|
var userId = _userManager.GetUserId(User);
|
||||||
await using var dbContext = _dbContextFactory.CreateContext();
|
await using var dbContext = _dbContextFactory.CreateContext();
|
||||||
var store = await dbContext.AppStorageItems
|
var store = await dbContext.AppStorageItems
|
||||||
.Where(data => data.Key == request.KeyValue.Key && data.UserId == userId &&
|
.Where(data => data.Key == request.KeyValue.Key && data.UserId == userId &&
|
||||||
data.Version == request.KeyValue.Version).ExecuteDeleteAsync(cancellationToken: cancellationToken);
|
data.Version == request.KeyValue.Version)
|
||||||
|
.ExecuteDeleteAsync(cancellationToken: cancellationToken);
|
||||||
return store == 0
|
return store == 0
|
||||||
? SetResult<DeleteObjectResponse>(
|
? SetResult<DeleteObjectResponse>(
|
||||||
new NotFoundObjectResult(new ErrorResponse()
|
new NotFoundObjectResult(new ErrorResponse()
|
||||||
|
@ -157,13 +160,15 @@ public class VSSController : Controller, IVSSAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(HttpVSSAPIClient.LIST_KEY_VERSIONS)]
|
[HttpPost(HttpVSSAPIClient.LIST_KEY_VERSIONS)]
|
||||||
public async Task<ListKeyVersionsResponse> ListKeyVersionsAsync(ListKeyVersionsRequest request, CancellationToken cancellationToken)
|
public async Task<ListKeyVersionsResponse> ListKeyVersionsAsync(ListKeyVersionsRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var userId = _userManager.GetUserId(User);
|
var userId = _userManager.GetUserId(User);
|
||||||
await using var dbContext = _dbContextFactory.CreateContext();
|
await using var dbContext = _dbContextFactory.CreateContext();
|
||||||
var items = await dbContext.AppStorageItems
|
var items = await dbContext.AppStorageItems
|
||||||
.Where(data => data.UserId == userId)
|
.Where(data => data.UserId == userId)
|
||||||
.Select(data => new KeyValue() {Key = data.Key, Version = data.Version}).ToListAsync(cancellationToken: cancellationToken);
|
.Select(data => new KeyValue() {Key = data.Key, Version = data.Version})
|
||||||
|
.ToListAsync(cancellationToken: cancellationToken);
|
||||||
return new ListKeyVersionsResponse {KeyVersions = {items}};
|
return new ListKeyVersionsResponse {KeyVersions = {items}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,7 +300,7 @@ public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
|
||||||
await _appState.InvoiceUpdate(identifier, lightningInvoice);
|
await _appState.InvoiceUpdate(identifier, lightningInvoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> DeviceMasterSignal(string deviceIdentifier, bool active)
|
public async Task<bool> DeviceMasterSignal(long deviceIdentifier, bool active)
|
||||||
{
|
{
|
||||||
return await _appState.DeviceMasterSignal(Context.ConnectionId,deviceIdentifier,active);
|
return await _appState.DeviceMasterSignal(Context.ConnectionId,deviceIdentifier,active);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace BTCPayServer.Controllers;
|
||||||
|
|
||||||
public record ConnectedInstance(
|
public record ConnectedInstance(
|
||||||
string UserId,
|
string UserId,
|
||||||
string DeviceIdentifier,
|
long? DeviceIdentifier,
|
||||||
bool Master,
|
bool Master,
|
||||||
// string ProvidedAccessKey,
|
// string ProvidedAccessKey,
|
||||||
HashSet<string> Groups);
|
HashSet<string> Groups);
|
||||||
|
@ -45,7 +45,7 @@ public class BTCPayAppState : IHostedService
|
||||||
private DerivationSchemeParser _derivationSchemeParser;
|
private DerivationSchemeParser _derivationSchemeParser;
|
||||||
|
|
||||||
|
|
||||||
public ConcurrentDictionary<string, ConnectedInstance> Connections { get; set; }
|
public ConcurrentDictionary<string, ConnectedInstance> Connections { get; set; } = new();
|
||||||
|
|
||||||
|
|
||||||
private CancellationTokenSource? _cts;
|
private CancellationTokenSource? _cts;
|
||||||
|
@ -266,24 +266,29 @@ public class BTCPayAppState : IHostedService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> DeviceMasterSignal(string contextConnectionId, string deviceIdentifier, bool active)
|
public async Task<bool> DeviceMasterSignal(string contextConnectionId, long deviceIdentifier, bool active)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!Connections.TryGetValue(contextConnectionId, out var connectedInstance))
|
if (!Connections.TryGetValue(contextConnectionId, out var connectedInstance))
|
||||||
{
|
{
|
||||||
|
_logger.LogWarning("DeviceMasterSignal called on non existing connection");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(!string.IsNullOrEmpty(connectedInstance.DeviceIdentifier) && connectedInstance.DeviceIdentifier != deviceIdentifier)
|
if(connectedInstance.DeviceIdentifier != null && connectedInstance.DeviceIdentifier != deviceIdentifier)
|
||||||
{
|
{
|
||||||
|
_logger.LogWarning("DeviceMasterSignal called with different device identifier");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(string.IsNullOrEmpty(connectedInstance.DeviceIdentifier))
|
if(connectedInstance.DeviceIdentifier == null)
|
||||||
{
|
{
|
||||||
Connections[contextConnectionId] = connectedInstance with {DeviceIdentifier = deviceIdentifier};
|
_logger.LogInformation("DeviceMasterSignal called with device identifier {deviceIdentifier}", deviceIdentifier);
|
||||||
|
connectedInstance = connectedInstance with {DeviceIdentifier = deviceIdentifier};
|
||||||
|
Connections[contextConnectionId] = connectedInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(connectedInstance.Master == active)
|
if(connectedInstance.Master == active)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("DeviceMasterSignal called with same active state");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (active)
|
else if (active)
|
||||||
|
@ -291,17 +296,22 @@ public class BTCPayAppState : IHostedService
|
||||||
//check if there is any other master connection with the same user id
|
//check if there is any other master connection with the same user id
|
||||||
if (Connections.Values.Any(c => c.UserId == connectedInstance.UserId && c.Master))
|
if (Connections.Values.Any(c => c.UserId == connectedInstance.UserId && c.Master))
|
||||||
{
|
{
|
||||||
|
_logger.LogWarning("DeviceMasterSignal called with active state but there is already a master connection");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Connections[contextConnectionId] = connectedInstance with {Master = true};
|
_logger.LogInformation("DeviceMasterSignal called with active state");
|
||||||
|
connectedInstance = connectedInstance with {Master = true};
|
||||||
|
Connections[contextConnectionId] = connectedInstance;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Connections[contextConnectionId] = connectedInstance with {Master = false};
|
_logger.LogInformation("DeviceMasterSignal called with inactive state");
|
||||||
|
connectedInstance = connectedInstance with {Master = false};
|
||||||
|
Connections[contextConnectionId] = connectedInstance;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -319,7 +329,7 @@ public class BTCPayAppState : IHostedService
|
||||||
|
|
||||||
public async Task Connected(string contextConnectionId, string userId)
|
public async Task Connected(string contextConnectionId, string userId)
|
||||||
{
|
{
|
||||||
Connections.TryAdd(contextConnectionId, new ConnectedInstance(userId, string.Empty, false, new HashSet<string>()));
|
Connections.TryAdd(contextConnectionId, new ConnectedInstance(userId, null, false, new HashSet<string>()));
|
||||||
|
|
||||||
if (_nodeInfo.Length > 0)
|
if (_nodeInfo.Length > 0)
|
||||||
await _hubContext.Clients.Client(contextConnectionId).NotifyServerNode(_nodeInfo);
|
await _hubContext.Clients.Client(contextConnectionId).NotifyServerNode(_nodeInfo);
|
||||||
|
|
Loading…
Add table
Reference in a new issue