Compare commits

..

1 Commits
master ... u2f

Author SHA1 Message Date
Kukks
d17ffd25ad x 2020-02-27 10:22:15 +01:00
187 changed files with 7836 additions and 10877 deletions

View File

@ -80,26 +80,12 @@ jobs:
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 --os linux --arch arm --variant v7
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 --os linux --arch arm64 --variant v8
sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG -p
trigger_docs_build:
machine:
enabled: true
image: circleci/classic:201808-01
steps:
- run:
command: |
curl -X POST -H "Authorization: token $GH_PAT" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/btcpayserver/btcpayserver-doc/dispatches --data '{"event_type": "build_docs"}'
workflows:
version: 2
publish:
jobs:
- trigger_docs_build:
filters:
branches:
only: master
# only act on version tags
tags:
only: /v[0-9]+(\.[0-9]+)*/
- publish_docker_linuxamd64:
filters:
# ignore any commit on any branch by default

View File

@ -1,7 +1,7 @@
@inject BTCTransmuter.Extension.Tor.Services.TorServices TorServices
@if (TorServices.TransmuterTorService != null)
{
<li class="nav-item">
<a href="@TorServices.TransmuterTorService.OnionHost" class="nav-link">Onion</a>
<li class="nav-item mr-3">
<a href="@TorServices.TransmuterTorService.OnionHost">Onion</a>
</li>
}

View File

@ -7,12 +7,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.0" />
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="3.1.4" />
<PackageReference Include="NetCore.AutoRegisterDi" Version="2.0.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.1.0" />
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="3.1.1" />
<PackageReference Include="NetCore.AutoRegisterDi" Version="1.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.1.1" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.20" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Data\BtcTransmuter.Data.csproj" />

View File

@ -1,5 +1,3 @@
using System;
namespace BtcTransmuter
{
public interface IBtcTransmuterOptions
@ -9,7 +7,5 @@ namespace BtcTransmuter
string DataProtectionDir { get; set; }
DatabaseType DatabaseType { get; set; }
bool UseDatabaseColumnEncryption { get; set; }
bool DisableInternalAuth { get; set; }
Uri BTCPayAuthServer { get; set; }
}
}

View File

@ -1,57 +0,0 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace BtcTransmuter.Abstractions.Extensions
{
public static class ControllerExtensions
{
public static bool IsApi(this Controller controller)
{
return controller.HttpContext.IsApi();
}
public static bool IsApi(this HttpContext context)
{
if (context.Items.TryGetValue("API", out var val))
{
return val is true;
}
return false;
}
public static void SetIsApi(this HttpContext context, bool val)
{
context.Items.TryAdd("API", val);
}
public static IActionResult ViewOrJson<T>(this Controller controller, string viewName, T payload)
{
if (controller.IsApi())
{
return controller.Json(payload);
}
return controller.View(viewName, payload);
}
public static IActionResult ViewOrJson<T>(this Controller controller, T payload)
{
if (controller.IsApi())
{
return controller.Json(payload);
}
return controller.View(payload);
}
public static IActionResult ViewOrBadRequest<T>(this Controller controller, T payload, bool usePayloadInBadRequest = false)
{
if (controller.IsApi())
{
return usePayloadInBadRequest ? controller.BadRequest(payload) : controller.BadRequest(controller.ModelState);
}
return controller.View(payload);
}
}
}

View File

@ -10,5 +10,16 @@ namespace BtcTransmuter.Abstractions.Extensions
{
return new HashSet<T>(source, comparer);
}
public static void AddOrReplace<TKey, TValue>(
this IDictionary<TKey, TValue> dico,
TKey key,
TValue value)
{
if (dico.ContainsKey(key))
dico[key] = value;
else
dico.Add(key, value);
}
}
}
}

View File

@ -1,16 +0,0 @@
using Microsoft.AspNetCore.Http;
namespace BtcTransmuter.Abstractions.Extensions
{
public static class RequestExtensions
{
public static string GetAbsoluteRoot(this HttpRequest request)
{
return string.Concat(
request.Scheme,
"://",
request.Host.ToUriComponent(),
request.PathBase.ToUriComponent());
}
}
}

View File

@ -35,7 +35,7 @@ namespace BtcTransmuter.Abstractions.ExternalServices
{
return result.Error;
}
return View(await BuildViewModel(result.Data));
}

View File

@ -62,7 +62,7 @@ namespace BtcTransmuter.Abstractions.Helpers
return (e.Compile().DynamicInvoke(data.Values.ToArray()) ?? "").ToString();
}
catch (Exception)
catch (Exception exception)
{
return processed;
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Entities.U2F;
namespace BtcTransmuter.Abstractions.U2F
{
public interface IU2FService
{
Task<List<U2FDevice>> GetDevices(string userId);
Task RemoveDevice(string id, string userId);
Task<bool> HasDevices(string userId);
ServerRegisterResponse StartDeviceRegistration(string userId, string appId);
Task<bool> CompleteRegistration(string userId, string deviceResponse, string name);
Task<bool> AuthenticateUser(string userId, string deviceResponse);
Task<List<ServerChallenge>> GenerateDeviceChallenges(string userId, string appId);
}
}

View File

@ -8,11 +8,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,10 @@
namespace BtcTransmuter.Data.Entities.U2F
{
public class ServerChallenge
{
public string challenge { get; set; }
public string version { get; set; }
public string appId { get; set; }
public string keyHandle { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace BtcTransmuter.Data.Entities.U2F
{
public class ServerRegisterResponse
{
public string AppId { get; set; }
public string Challenge { get; set; }
public string Version { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace BtcTransmuter.Data.Entities.U2F
{
public class U2FDeviceAuthenticationRequest
{
public string KeyHandle { get; set; }
[Required] public string Challenge { get; set; }
[Required] [StringLength(200)] public string AppId { get; set; }
[Required] [StringLength(50)] public string Version { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
namespace BtcTransmuter.Data.Entities
{
public class U2FDevice
{
public string Id { get; set; }
public string Name { get; set; }
[Required] public byte[] KeyHandle { get; set; }
[Required] public byte[] PublicKey { get; set; }
[Required] public byte[] AttestationCert { get; set; }
[Required] public int Counter { get; set; }
public string UserId { get; set; }
public User User { get; set; }
}
}

View File

@ -1,26 +1,11 @@
using System.Collections.Generic;
using BtcTransmuter.Data.Encryption;
using BtcTransmuter.Data.Models;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
namespace BtcTransmuter.Data.Entities
{
public class User : IdentityUser, IHasJsonData
public class User : IdentityUser
{
public List<Recipe> Recipes { get; set; }
[Encrypted] public string DataJson { get; set; }
public List<U2FDevice> U2FDevices { get; set; }
}
public class UserBlob
{
public bool BasicAuth { get; set; }
public BTCPayAuthDetails BTCPayAuthDetails { get; set; } = new BTCPayAuthDetails();
}
public class BTCPayAuthDetails
{
public string UserId { get; set; }
public string AccessToken { get; set; }
}
}
}

View File

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -4,13 +4,13 @@
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
<ProjectReference Include="..\BtcTransmuter.Extension.DynamicServices\BtcTransmuter.Extension.DynamicServices.csproj" />
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj"/>
<ProjectReference Include="..\BtcTransmuter.Extension.DynamicServices\BtcTransmuter.Extension.DynamicServices.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.4" />
<PackageReference Include="NBitcoin" Version="5.0.40" />
<PackageReference Include="NBitpayClient" Version="1.0.0.38" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.1"/>
<PackageReference Include="NBitcoin" Version="5.0.13"/>
<PackageReference Include="NBitpayClient" Version="1.0.0.37"/>
</ItemGroup>
</Project>

View File

@ -52,7 +52,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
var client = ConstructClient();
return client != null && await client.TestAccessAsync(Facade.Merchant);
}
catch (Exception)
catch (Exception e)
{
return false;
}

View File

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -5,9 +5,9 @@
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.1"/>
</ItemGroup>
</Project>

View File

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -5,11 +5,11 @@
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
<ProjectReference Include="..\BtcTransmuter.Extension.DynamicServices\BtcTransmuter.Extension.DynamicServices.csproj" />
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj"/>
<ProjectReference Include="..\BtcTransmuter.Extension.DynamicServices\BtcTransmuter.Extension.DynamicServices.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="MailKit" Version="2.6.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.4" />
<PackageReference Include="MailKit" Version="2.4.1"/>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.1"/>
</ItemGroup>
</Project>

View File

@ -51,6 +51,7 @@ namespace BtcTransmuter.Extension.Email.ExternalServices.Smtp
var error = await SendTestEmail(smtpService, viewModel.TestEmail);
if (string.IsNullOrEmpty(error))
{
ModelState.AddModelError(nameof(viewModel.TestEmail), "Email sent successfully, confirm that you received it");
viewModel.TestEmail = string.Empty;
}
else
@ -98,4 +99,4 @@ namespace BtcTransmuter.Extension.Email.ExternalServices.Smtp
[Display(Name = "Send test email from and to this address to check if your settings are valid")]
public string TestEmail { get; set; }
}
}
}

View File

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -2,7 +2,6 @@ using System;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Models;
using System.Linq;
using System.Threading.Tasks;
using BtcTransmuter.Extension.Exchange.ExternalServices.Exchange;
using BtcTransmuter.Tests.Base;
using Xunit;
@ -13,9 +12,9 @@ namespace BtcTransmuter.Extension.Exchange.Tests
public class ExchangeServiceTests:BaseExternalServiceTest<ExchangeService,ExchangeExternalServiceData >
{
[Fact]
public async Task ExchangeService_GetAvailableExchanges()
public void ExchangeService_GetAvailableExchanges()
{
Assert.True((await ExchangeService.GetAvailableExchanges()).Any());
Assert.True(ExchangeService.GetAvailableExchanges().Any());
}
[Fact]
@ -50,7 +49,7 @@ namespace BtcTransmuter.Extension.Exchange.Tests
}
[Fact]
public async Task ExchangeService_CanConstructClient()
public void ExchangeService_CanConstructClient()
{
var InvalidData = new ExchangeExternalServiceData()
{
@ -65,7 +64,7 @@ namespace BtcTransmuter.Extension.Exchange.Tests
externalServiceData.Set(InvalidData);
var exchangeService = GetExternalService(externalServiceData);
await Assert.ThrowsAnyAsync<Exception>(async () => await exchangeService.ConstructClient());
Assert.ThrowsAny<Exception>(() => exchangeService.ConstructClient());
var validData = new ExchangeExternalServiceData()

View File

@ -36,7 +36,7 @@ namespace BtcTransmuter.Extension.Exchange.Actions.GetExchangeBalance
var serviceData =
await _externalServiceManager.GetExternalServiceData(externalServiceId, GetUserId());
var exchangeService = new ExchangeService(serviceData);
var symbols = await (await exchangeService.ConstructClient()).GetCurrenciesAsync();
var symbols = await exchangeService.ConstructClient().GetCurrenciesAsync();
return symbols.Keys.ToArray();
}

View File

@ -28,7 +28,7 @@ namespace BtcTransmuter.Extension.Exchange.Actions.GetExchangeBalance
var externalService = await recipeAction.GetExternalService();
var exchangeService = new ExchangeService(externalService);
var client = await exchangeService.ConstructClient();
var client = exchangeService.ConstructClient();
var result = await client.GetAmountsAsync();

View File

@ -55,7 +55,7 @@ namespace BtcTransmuter.Extension.Exchange.Actions.GetExchangeRate
var serviceData =
await _externalServiceManager.GetExternalServiceData(viewModel.ExternalServiceId, GetUserId());
var exchangeService = new ExchangeService(serviceData);
var symbols = (await (await exchangeService.ConstructClient()).GetMarketSymbolsAsync()).ToArray();
var symbols = (await exchangeService.ConstructClient().GetMarketSymbolsAsync()).ToArray();
if (symbols.Contains(viewModel.MarketSymbol))
{
mainModel.ExternalServiceId = viewModel.ExternalServiceId;

View File

@ -26,7 +26,7 @@ namespace BtcTransmuter.Extension.Exchange.Actions.GetExchangeRate
{
var externalService = await recipeAction.GetExternalService();
var exchangeService = new ExchangeService(externalService);
var client = await exchangeService.ConstructClient();
var client = exchangeService.ConstructClient();
var result = await client.GetTickerAsync(actionData.MarketSymbol);

View File

@ -38,7 +38,7 @@ namespace BtcTransmuter.Extension.Exchange.Actions.PlaceOrder
var serviceData =
await _externalServiceManager.GetExternalServiceData(externalServiceId, GetUserId());
var exchangeService = new ExchangeService(serviceData);
return (await (await exchangeService.ConstructClient()).GetMarketSymbolsAsync()).ToArray();
return (await exchangeService.ConstructClient().GetMarketSymbolsAsync()).ToArray();
}
protected override async Task<PlaceOrderViewModel> BuildViewModel(RecipeAction from)
@ -79,7 +79,7 @@ namespace BtcTransmuter.Extension.Exchange.Actions.PlaceOrder
var serviceData =
await _externalServiceManager.GetExternalServiceData(viewModel.ExternalServiceId, GetUserId());
var exchangeService = new ExchangeService(serviceData);
var symbols = (await (await exchangeService.ConstructClient()).GetMarketSymbolsAsync()).ToArray();
var symbols = (await exchangeService.ConstructClient().GetMarketSymbolsAsync()).ToArray();
if (symbols.Contains(viewModel.MarketSymbol))
{
mainModel.ExternalServiceId = viewModel.ExternalServiceId;

View File

@ -26,7 +26,7 @@ namespace BtcTransmuter.Extension.Exchange.Actions.PlaceOrder
{
var externalService = await recipeAction.GetExternalService();
var exchangeService = new ExchangeService(externalService);
var client = await exchangeService.ConstructClient();
var client = exchangeService.ConstructClient();
var orderRequest = new ExchangeOrderRequest()
{
MarketSymbol = actionData.MarketSymbol,
@ -43,7 +43,7 @@ namespace BtcTransmuter.Extension.Exchange.Actions.PlaceOrder
{
var result = await client.PlaceOrderAsync(orderRequest);
System.Threading.Thread.Sleep(500);
result = await client.GetOrderDetailsAsync(result.OrderId, orderRequest.MarketSymbol);
result = await client.GetOrderDetailsAsync(result.OrderId);
return new TypedActionHandlerResult<ExchangeOrderResult>()
{
Executed = true,
@ -63,4 +63,4 @@ namespace BtcTransmuter.Extension.Exchange.Actions.PlaceOrder
}
}
}
}
}

View File

@ -4,6 +4,9 @@
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
<ProjectReference Include="..\BtcTransmuter.Extension.DynamicServices\BtcTransmuter.Extension.DynamicServices.csproj" />
@ -11,7 +14,4 @@
<ItemGroup>
<UpToDateCheckInput Remove="Views\SendEmail\EditData.cshtml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Custom.DigitalRuby.ExchangeSharp" Version="0.9.0" />
</ItemGroup>
</Project>

View File

@ -25,10 +25,10 @@ namespace BtcTransmuter.Extension.Exchange.ExternalServices.Exchange
protected override string ExternalServiceType => ExchangeService.ExchangeServiceType;
protected override async Task<EditExchangeExternalServiceDataViewModel> BuildViewModel(ExternalServiceData data)
protected override Task<EditExchangeExternalServiceDataViewModel> BuildViewModel(ExternalServiceData data)
{
return new EditExchangeExternalServiceDataViewModel(new ExchangeService(data).GetData(),
await ExchangeService.GetAvailableExchanges());
return Task.FromResult(new EditExchangeExternalServiceDataViewModel(new ExchangeService(data).GetData(),
ExchangeService.GetAvailableExchanges()));
}
protected override async
@ -41,7 +41,7 @@ namespace BtcTransmuter.Extension.Exchange.ExternalServices.Exchange
if (!ModelState.IsValid)
{
return (null,
new EditExchangeExternalServiceDataViewModel(viewModel, await ExchangeService.GetAvailableExchanges()));
new EditExchangeExternalServiceDataViewModel(viewModel, ExchangeService.GetAvailableExchanges()));
}
//current External Service data
@ -54,7 +54,7 @@ namespace BtcTransmuter.Extension.Exchange.ExternalServices.Exchange
"Could not connect with current settings. Transmuter tests against fetching your balance amount from the exchange so you would need to enable that option if available");
return (null,
new EditExchangeExternalServiceDataViewModel(viewModel, await ExchangeService.GetAvailableExchanges()));
new EditExchangeExternalServiceDataViewModel(viewModel, ExchangeService.GetAvailableExchanges()));
}
return (externalServiceData, null);

View File

@ -25,17 +25,17 @@ namespace BtcTransmuter.Extension.Exchange.ExternalServices.Exchange
{
}
public static async Task<IExchangeAPI[]> GetAvailableExchanges()
public static IExchangeAPI[] GetAvailableExchanges()
{
return await ExchangeAPI.GetExchangeAPIsAsync();
return ExchangeAPI.GetExchangeAPIs();
}
public async Task<ExchangeAPI> ConstructClient()
public ExchangeAPI ConstructClient()
{
var data = GetData();
var result = await ExchangeAPI.GetExchangeAPIAsync(data.ExchangeName);
var result = ExchangeAPI.GetExchangeAPI(data.ExchangeName);
if (result is ExchangeAPI api)
{
if (!string.IsNullOrEmpty(data.OverrideUrl))
@ -52,7 +52,7 @@ namespace BtcTransmuter.Extension.Exchange.ExternalServices.Exchange
public async Task<bool> TestAccess()
{
var client = await ConstructClient();
var client = ConstructClient();
if (client == null)
{
return false;

View File

@ -52,7 +52,7 @@ namespace BtcTransmuter.Extension.NBXplorer.HostedServices
await Task.WhenAll(exchangeExternalServices.Select(async data =>
{
var exchangeService = new ExchangeService(data);
var client = await exchangeService.ConstructClient();
var client = exchangeService.ConstructClient();
var amounts = await client.GetAmountsAsync();
foreach (var keyValuePair in amounts)
{

View File

@ -37,7 +37,7 @@ namespace BtcTransmuter.Extension.Exchange.Triggers.CheckExchangeBalance
var serviceData =
await _externalServiceManager.GetExternalServiceData(externalServiceId, GetUserId());
var exchangeService = new ExchangeService(serviceData);
var symbols = await (await exchangeService.ConstructClient()).GetCurrenciesAsync();
var symbols = await exchangeService.ConstructClient().GetCurrenciesAsync();
return symbols.Keys.ToArray();
}

View File

@ -10,9 +10,9 @@
<ItemGroup>
<PackageReference Include="MaxKagamine.Moq.HttpClient" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -5,8 +5,8 @@
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.15" />
<PackageReference Include="NBitcoin" Version="5.0.40" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.8" />
<PackageReference Include="NBitcoin" Version="5.0.13" />
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
<ProjectReference Include="..\BtcTransmuter.Extension.DynamicServices\BtcTransmuter.Extension.DynamicServices.csproj" />
<ProjectReference Include="..\BtcTransmuter.Extension.NBXplorer\BtcTransmuter.Extension.NBXplorer.csproj" />

View File

@ -11,9 +11,9 @@
<ItemGroup>
<PackageReference Include="MaxKagamine.Moq.HttpClient" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -9,7 +9,7 @@
<ProjectReference Include="..\BtcTransmuter.Extension.DynamicServices\BtcTransmuter.Extension.DynamicServices.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NBXplorer.Client" Version="3.0.15" />
<PackageReference Include="NBXplorer.Client" Version="3.0.2" />
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Views\NBXplorerCreatePSBT\EditData.cshtml" />

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BtcTransmuter.Extension.NBXplorer.Models;
@ -33,30 +34,28 @@ namespace BtcTransmuter.Extension.NBXplorer.Controllers
});
}
[HttpPost("{cryptoCode}")]
public IActionResult GetWallet(string cryptoCode, [FromForm] string mnemonic)
[HttpGet("{cryptoCode}/{mnemonic?}")]
public IActionResult GetWallet(string cryptoCode, string mnemonic)
{
return ActionResult(cryptoCode, string.IsNullOrEmpty(mnemonic) ? null : new Mnemonic(mnemonic));
}
if (string.IsNullOrEmpty(mnemonic))
{
return RedirectToAction("GetWallet", new
{
cryptoCode = cryptoCode,
mnemonic = new Mnemonic(Wordlist.English).ToString()
});
}
[HttpGet("{cryptoCode}")]
public IActionResult GetWallet(string cryptoCode)
{
return ActionResult(cryptoCode);
}
private IActionResult ActionResult(string cryptoCode, Mnemonic mnemonic = null)
{
var network = _nbXplorerClientProvider.GetClient(cryptoCode).Network;
var addressTypes = new Dictionary<ScriptPubKeyType, GetWalletViewModel.GetWalletViewModelAddressType>();
var mnemonicSeed = mnemonic ?? new Mnemonic(Wordlist.English);
var mnemonicSeed = new Mnemonic(mnemonic);
var extKey = mnemonicSeed.DeriveExtKey();
var wif = extKey.GetWif(network.NBitcoinNetwork);
var privateKey = extKey.PrivateKey;
var secret = privateKey.GetBitcoinSecret(network.NBitcoinNetwork);
if (network.NBitcoinNetwork.Consensus.SupportSegwit)
{
var segwitExtPubkey = extKey
@ -71,26 +70,24 @@ namespace BtcTransmuter.Extension.NBXplorer.Controllers
var segwit = _derivationSchemeParser.Parse(network.DerivationStrategyFactory, $"{segwitExtPubkey}");
var p2sh = _derivationSchemeParser.Parse(network.DerivationStrategyFactory, $"{p2shExtPubKey}-[p2sh]");
addressTypes.Add(ScriptPubKeyType.Segwit, new GetWalletViewModel.GetWalletViewModelAddressType()
{
Description = "BTCPay / BTCTransmuter compatible xpub for segwit addresses",
DerivationScheme = segwit.ToString(),
Addresses = GenerateAddresses(segwit, network),
RootKeyPath ="m/" + NBXplorerPublicWallet.GetDerivationKeyPath(ScriptPubKeyType.Segwit, 0, network)
.ToString()
RootKeyPath = NBXplorerPublicWallet.GetDerivationKeyPath(ScriptPubKeyType.Segwit, 0, network).ToString()
});
addressTypes.Add(ScriptPubKeyType.SegwitP2SH, new GetWalletViewModel.GetWalletViewModelAddressType()
{
Description = "BTCPay / BTCTransmuter compatible xpub for p2sh addresses",
DerivationScheme = p2sh.ToString(),
Addresses = GenerateAddresses(p2sh, network),
RootKeyPath = "m/" + NBXplorerPublicWallet.GetDerivationKeyPath(ScriptPubKeyType.SegwitP2SH, 0, network)
.ToString()
RootKeyPath = NBXplorerPublicWallet.GetDerivationKeyPath(ScriptPubKeyType.SegwitP2SH, 0, network).ToString()
});
}
var legacyExtPubkey = extKey
.Derive(NBXplorerPublicWallet.GetDerivationKeyPath(ScriptPubKeyType.Legacy, 0, network)).Neuter()
.ToString(network.NBitcoinNetwork);
@ -102,19 +99,18 @@ namespace BtcTransmuter.Extension.NBXplorer.Controllers
Description = "BTCPay / BTCTransmuter compatible xpub for legacy addresses",
DerivationScheme = legacy.ToString(),
Addresses = GenerateAddresses(legacy, network),
RootKeyPath = "m/" +NBXplorerPublicWallet.GetDerivationKeyPath(ScriptPubKeyType.Legacy, 0, network).ToString()
RootKeyPath = NBXplorerPublicWallet.GetDerivationKeyPath(ScriptPubKeyType.Legacy, 0, network).ToString()
});
return View(new GetWalletViewModel()
{
Mnemonic = mnemonicSeed.ToString(),
Mnemonic = mnemonic,
Network = network,
CryptoCode = cryptoCode,
CryptoCodes = _nbXplorerOptions.Cryptos,
PrivateKey = privateKey,
WIF = wif,
ExtPubKey = extKey.Neuter().ToString(network.NBitcoinNetwork),
Fingerprint = extKey.Neuter().PubKey.GetHDFingerPrint().ToString(),
AddressTypes = addressTypes,
Address = secret.GetAddress(ScriptPubKeyType.Legacy),
SegwitAddress = network.NBitcoinNetwork.Consensus.SupportSegwit
@ -122,8 +118,7 @@ namespace BtcTransmuter.Extension.NBXplorer.Controllers
: null,
P2SHAddress = network.NBitcoinNetwork.Consensus.SupportSegwit
? secret.GetAddress(ScriptPubKeyType.SegwitP2SH)
: null,
: null
});
}
@ -152,8 +147,8 @@ namespace BtcTransmuter.Extension.NBXplorer.Controllers
public Key PrivateKey { get; set; }
public BitcoinExtKey WIF { get; set; }
public string ExtPubKey { get; set; }
public string Fingerprint { get; set; }
public Dictionary<ScriptPubKeyType, GetWalletViewModelAddressType> AddressTypes { get; set; }
public Dictionary<ScriptPubKeyType,GetWalletViewModelAddressType> AddressTypes { get; set; }
public BitcoinAddress SegwitAddress { get; set; }
public BitcoinAddress Address { get; set; }
public BitcoinAddress P2SHAddress { get; set; }
@ -163,7 +158,7 @@ namespace BtcTransmuter.Extension.NBXplorer.Controllers
public string Description { get; set; }
public string DerivationScheme { get; set; }
public string RootKeyPath { get; set; }
public Dictionary<string, string> Addresses { get; set; }
public Dictionary<string , string> Addresses { get; set; }
}
}
}

View File

@ -95,7 +95,7 @@ namespace BtcTransmuter.Extension.NBXplorer.Services
return txBuilder.BuildTransaction(true);
}
private Task<TransactionBuilder> AddTxOutsToTransaction(TransactionBuilder transactionBuilder,
public async Task<TransactionBuilder> AddTxOutsToTransaction(TransactionBuilder transactionBuilder,
IEnumerable<(Money amount, IDestination destination, bool subtractFee)> outgoing)
{
foreach (var tuple in outgoing)
@ -119,7 +119,7 @@ namespace BtcTransmuter.Extension.NBXplorer.Services
break;
}
return Task.FromResult(transactionBuilder);
return transactionBuilder;
}
public static ExtKey GetKeyFromDetails(PrivateKeyDetails privateKeyDetails, Network network)

View File

@ -2,8 +2,8 @@
{
if (User.IsInRole("Admin"))
{
<li class="nav-item">
<a asp-controller="NBXplorerStatus" asp-action="GetSummaries" class="nav-link">NBXplorer Status</a>
<li class="nav-item mr-3">
<a asp-controller="NBXplorerStatus" asp-action="GetSummaries">NBXplorer Status</a>
</li>
}
}
}

View File

@ -20,12 +20,7 @@
<div class="card-body">
<div class="form-group">
<label class="control-label">Mnemonic Seed</label>
<form class="input-group" method="post" asp-action="GetWallet" asp-route-cryptoCode="@Model.CryptoCode">
<input asp-for="Mnemonic" class="form-control"/>
<div class="input-group-append">
<button type="submit" class="btn btn-secondary">Load</button>
</div>
</form>
<input readonly class="form-control" value="@Model.Mnemonic"/>
</div>
<div class="form-group">
<label class="control-label">Master Private Key</label>
@ -39,17 +34,13 @@
<label class="control-label">Master Extended Public key</label>
<input readonly class="form-control" value="@Model.ExtPubKey"/>
</div>
<div class="form-group">
<label class="control-label">Root Fingerprint</label>
<input readonly class="form-control" value="@Model.Fingerprint"/>
</div>
<div class="card">
<nav>
<div class="nav nav-tabs nav-fill" id="nav-tab" role="tablist">
@for (int i = 0; i < Model.AddressTypes.Count; i++)
{
var item = Model.AddressTypes.ElementAt(i);
<a class="nav-item nav-link @(i == 0 ? "active" : "")" href="#tab-hd-@item.Key" id="nav-hd-@item.Key" data-toggle="tab" h role="tab">@item.Key</a>
@for (int i = 0; i < Model.AddressTypes.Count; i++)
{
var item = Model.AddressTypes.ElementAt(i);
<a class="nav-item nav-link @(i == 0? "active": "")" href="#tab-hd-@item.Key" id="nav-hd-@item.Key" data-toggle="tab" h role="tab">@item.Key</a>
}
<a class="nav-item nav-link" href="#tab-singular" id="nav-singular" data-toggle="tab" role="tab">Singular Addresses</a>
@ -59,15 +50,11 @@
@for (int i = 0; i < Model.AddressTypes.Count; i++)
{
var item = Model.AddressTypes.ElementAt(i);
<div class="tab-pane p-2 @(i == 0 ? "show active" : "")" id="tab-hd-@item.Key" role="tabpanel" id="tab-hd-@item.Key">
<div class="tab-pane p-2 @(i == 0? "show active": "")" id="tab-hd-@item.Key" role="tabpanel" id="tab-hd-@item.Key">
<div class="form-group">
<label class="control-label">@item.Value.Description</label>
<input readonly class="form-control" value="@item.Value.DerivationScheme"/>
</div>
<div class="form-group">
<label class="control-label">Account Key Path</label>
<input readonly class="form-control" value="@item.Value.RootKeyPath"/>
</div>
<div class="form-group">
<table class="table table-sm table-responsive-md">
<thead>
@ -80,15 +67,14 @@
@foreach (var sample in item.Value.Addresses)
{
<tr>
<td>
<span class="text-muted ">@item.Value.RootKeyPath/</span>@sample.Key</td>
<td><span class="text-muted ">@item.Value.RootKeyPath/</span>@sample.Key</td>
<td>@sample.Value</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
<div class="tab-pane p-2" id="tab-singular" role="tabpanel">

View File

@ -11,9 +11,9 @@
<ItemGroup>
<PackageReference Include="MaxKagamine.Moq.HttpClient" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -24,14 +24,14 @@ namespace BtcTransmuter.Extension.Operators.Actions.ChooseNumericValue
{
}
protected override Task<ChooseNumericValueViewModel> BuildViewModel(RecipeAction from)
protected override async Task<ChooseNumericValueViewModel> BuildViewModel(RecipeAction from)
{
var fromData = from.Get<ChooseNumericValueData>();
return Task.FromResult(new ChooseNumericValueViewModel
return new ChooseNumericValueViewModel
{
RecipeId = @from.RecipeId,
Items = fromData.Items
});
};
}
protected override async Task<(RecipeAction ToSave, ChooseNumericValueViewModel showViewModel)> BuildModel(

View File

@ -18,7 +18,7 @@ namespace BtcTransmuter.Extension.Operators.Actions.ChooseNumericValue
public override string ControllerName => "ChooseNumericValue";
protected override Task<TypedActionHandlerResult<string>> Execute(Dictionary<string, object> data, RecipeAction recipeAction,
protected override async Task<TypedActionHandlerResult<string>> Execute(Dictionary<string, object> data, RecipeAction recipeAction,
ChooseNumericValueData actionData)
{
ChooseNumericValueData.ChooseNumericValueDataItem selectedItem = null;
@ -53,12 +53,12 @@ namespace BtcTransmuter.Extension.Operators.Actions.ChooseNumericValue
}
});
return Task.FromResult(new TypedActionHandlerResult<string>()
return new TypedActionHandlerResult<string>()
{
TypedData = selectedItem.ValueToChoose,
Executed = true,
Result = $"chose {selectedItem.ValueToChoose}"
});
};
}
}
}

View File

@ -24,14 +24,14 @@ namespace BtcTransmuter.Extension.Operators.Actions.Condition
{
}
protected override Task<ConditionViewModel> BuildViewModel(RecipeAction from)
protected override async Task<ConditionViewModel> BuildViewModel(RecipeAction from)
{
var fromData = from.Get<ConditionData>();
return Task.FromResult(new ConditionViewModel
return new ConditionViewModel
{
RecipeId = @from.RecipeId,
Condition = fromData.Condition
});
};
}
protected override async Task<(RecipeAction ToSave, ConditionViewModel showViewModel)> BuildModel(

View File

@ -18,17 +18,18 @@ namespace BtcTransmuter.Extension.Operators.Actions.Condition
public override string ControllerName => "Condition";
protected override Task<TypedActionHandlerResult<string>> Execute(Dictionary<string, object> data, RecipeAction recipeAction,
protected override async Task<TypedActionHandlerResult<string>> Execute(Dictionary<string, object> data, RecipeAction recipeAction,
ConditionData actionData)
{
var condition = InterpolateString(actionData.Condition, data);
return Task.FromResult(new TypedActionHandlerResult<string>()
return new TypedActionHandlerResult<string>()
{
TypedData = condition,
Executed = condition.Equals("true", StringComparison.InvariantCultureIgnoreCase),
Result = $"Data value was: {condition}"
});
};
}
}
}

View File

@ -1 +0,0 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -10,6 +10,5 @@
<ProjectReference Include="..\BtcTransmuter.Extension.Email\BtcTransmuter.Extension.Email.csproj" />
<ProjectReference Include="..\BtcTransmuter.Extension.Exchange\BtcTransmuter.Extension.Exchange.csproj" />
<ProjectReference Include="..\BtcTransmuter.Extension.NBXplorer\BtcTransmuter.Extension.NBXplorer.csproj" />
<ProjectReference Include="..\BtcTransmuter.Extension.Timer\BtcTransmuter.Extension.Timer.csproj" />
</ItemGroup>
</Project>

View File

@ -1,202 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.Extensions;
using BtcTransmuter.Abstractions.ExternalServices;
using BtcTransmuter.Abstractions.Recipes;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Models;
using BtcTransmuter.Extension.Exchange.Actions.PlaceOrder;
using BtcTransmuter.Extension.Exchange.ExternalServices.Exchange;
using BtcTransmuter.Extension.Timer.Triggers.Timer;
using ExchangeSharp;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Newtonsoft.Json;
namespace BtcTransmuter.Extension.Presets
{
[Route("presets-plugin/presets/dca")]
[Authorize]
public class DCAController : Controller, ITransmuterPreset
{
private readonly IExternalServiceManager _externalServiceManager;
private readonly UserManager<User> _userManager;
private readonly IRecipeManager _recipeManager;
public string Id { get; } = "DCA";
public string Name { get; } = "Dollar Cost Average";
public string Description { get; } = "Schedule daily purchases of Bitcoin!";
public DCAController(
IExternalServiceManager externalServiceManager,
UserManager<User> userManager,
IRecipeManager recipeManager)
{
_externalServiceManager = externalServiceManager;
_userManager = userManager;
_recipeManager = recipeManager;
}
public (string ControllerName, string ActionName) GetLink()
{
return (Id, nameof(Create));
}
[HttpGet("create")]
public async Task<IActionResult> Create()
{
var services = await GetServices();
return View(new CreateDCAViewModel()
{
ExchangeServices = new SelectList(services, nameof(ExternalServiceData.Id), nameof(ExternalServiceData.Name))
});
}
private async Task<IEnumerable<ExternalServiceData>> GetServices()
{
var services = await _externalServiceManager.GetExternalServicesData(new ExternalServicesDataQuery()
{
UserId = _userManager.GetUserId(User),
Type = new[] {Exchange.ExternalServices.Exchange.ExchangeService.ExchangeServiceType}
});
var exchangeServices = services.Where(data => data.Type == Exchange.ExternalServices.Exchange.ExchangeService.ExchangeServiceType);
return exchangeServices;
}
[HttpPost("create")]
public async Task<IActionResult> Create(CreateDCAViewModel viewModel)
{
var services = await GetServices();
viewModel.ExchangeServices = new SelectList(services, nameof(ExternalServiceData.Id),
nameof(ExternalServiceData.Name));
if (viewModel.FiatAmount <= 0)
{
ModelState.AddModelError(nameof(viewModel.FiatAmount), "Amount needs to be more than 0.");
}
if (ModelState.IsValid)
{
var serviceData =
await _externalServiceManager.GetExternalServiceData(viewModel.SelectedExchangeServiceId, GetUserId());
var exchangeService = new ExchangeService(serviceData);
var symbols = (await (await exchangeService.ConstructClient()).GetMarketSymbolsAsync()).ToArray();
if (!symbols.Contains(viewModel.MarketSymbol))
{
viewModel.AddModelError(nameof(viewModel.MarketSymbol), $"The market symbols you entered is invalid. Please choose from the following: {string.Join(",", symbols)}", ModelState);
}
}
if (!ModelState.IsValid)
{
return View(viewModel);
}
return await SetItUp(viewModel);
}
protected string GetUserId()
{
return _userManager.GetUserId(User);
}
private async Task<IActionResult> SetItUp(CreateDCAViewModel vm)
{
var presetName = $"Generated_DCA";
var recipe = new Recipe()
{
Name = presetName,
Description = "Generated from a preset",
UserId = _userManager.GetUserId(User),
Enabled = false
};
await _recipeManager.AddOrUpdateRecipe(recipe);
var recipeTrigger = new RecipeTrigger()
{
TriggerId = new TimerTrigger().Id,
RecipeId = recipe.Id
};
recipeTrigger.Set(new TimerTriggerParameters()
{
StartOn = vm.StartOn,
TriggerEvery = vm.TriggerEvery,
TriggerEveryAmount = vm.TriggerEveryAmount
});
await _recipeManager.AddOrUpdateRecipeTrigger(recipeTrigger);
var recipeActionGroup = new RecipeActionGroup()
{
RecipeId = recipe.Id
};
await _recipeManager.AddRecipeActionGroup(recipeActionGroup);
var tradeAction = new RecipeAction()
{
RecipeId = recipe.Id,
RecipeActionGroupId = recipeActionGroup.Id,
ActionId = new PlaceOrderDataActionHandler().ActionId,
ExternalServiceId = vm.SelectedExchangeServiceId,
Order = 0,
DataJson = JsonConvert.SerializeObject(new PlaceOrderData()
{
Amount = vm.FiatAmount.ToString(CultureInfo.InvariantCulture),
IsBuy = vm.IsBuy,
MarketSymbol = vm.MarketSymbol,
OrderType = OrderType.Market
})
};
await _recipeManager.AddOrUpdateRecipeAction(tradeAction);
return RedirectToAction("EditRecipe", "Recipes", new
{
id = recipe.Id,
statusMessage =
"Preset generated. Recipe is currently disabled for now. Please verify details are correct before enabling!"
});
}
}
public class CreateDCAViewModel
{
public SelectList ExchangeServices { get; set; }
[Display(Name = "Existing Exchange Store")]
[Required]
public string SelectedExchangeServiceId { get; set; }
[Display(Name = "The trading pair on the exchange")]
[Required]
public string MarketSymbol { get; set; }
[Display(Name = "Is it a buy market order?")]
[Required]
public bool IsBuy { get; set; } = true;
[Display(Name = "How much do you want to buy?")]
[Required]
public decimal FiatAmount { get; set; }
[Required]
[Display(Name = "Trigger every")]
public int TriggerEveryAmount { get; set; } = 1;
[Required]
public TimerTriggerParameters.TimerResetEvery TriggerEvery { get; set; } =
TimerTriggerParameters.TimerResetEvery.Day;
[Display(Name = "Start from")]
public DateTime? StartOn { get; set; }
}
}

View File

@ -113,7 +113,7 @@ namespace BtcTransmuter.Extension.Presets
var serviceData =
await _externalServiceManager.GetExternalServiceData(condition.ExchangeServiceId, GetUserId());
var exchangeService = new ExchangeService(serviceData);
var symbols = (await (await exchangeService.ConstructClient()).GetMarketSymbolsAsync()).ToArray();
var symbols = (await exchangeService.ConstructClient().GetMarketSymbolsAsync()).ToArray();
if (!symbols.Contains(condition.MarketSymbol))
{
viewModel.AddModelError(

View File

@ -14,7 +14,6 @@ namespace BtcTransmuter.Extension.Presets
serviceCollection.AddTransient<ITransmuterPreset, PaymentForwarderController>();
serviceCollection.AddTransient<ITransmuterPreset, BTCPayEmailReceiptsController>();
serviceCollection.AddTransient<ITransmuterPreset, FiatExchangeConversionController>();
serviceCollection.AddTransient<ITransmuterPreset, DCAController>();
}
}
}

View File

@ -17,12 +17,12 @@ namespace BtcTransmuter.Extension.Presets
}
[HttpGet]
public Task<IActionResult> ChoosePreset()
public async Task<IActionResult> ChoosePreset()
{
return Task.FromResult<IActionResult>(View(new ChoosePresetViewModel()
return View(new ChoosePresetViewModel()
{
Presets = _transmuterPresets
}));
});
}
public class ChoosePresetViewModel
{

View File

@ -1,126 +0,0 @@
@using System.Globalization
@using BtcTransmuter.Extension.Exchange.ExternalServices.Exchange
@using BtcTransmuter.Extension.Timer.Triggers.Timer
@model BtcTransmuter.Extension.Presets.CreateDCAViewModel
@{
ViewData["Title"] = "Create automated Dollar Cost Averaging";
}
<h2>@ViewData["Title"]</h2>
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="card mb-2">
<div class="card-body">
<h5 class="card-title">Choose your Exchange</h5>
<div class="form-group">
@if (!Model.ExchangeServices.Any())
{
<div class="list-group-item ">
<span class="text-danger">
There are no Exchange services connected to your Transmuter account.
<a asp-controller="ExternalServices" asp-action="CreateExternalService" asp-route-selectedType="@ExchangeService.ExchangeServiceType">Please create one first.</a>
</span>
</div>
}
else
{
<select asp-for="SelectedExchangeServiceId" asp-items="Model.ExchangeServices" class="form-control"></select>
<a asp-controller="ExternalServices" asp-action="CreateExternalService" asp-route-selectedType="@ExchangeService.ExchangeServiceType">Create</a>
}
<span asp-validation-for="SelectedExchangeServiceId" class="text-danger"></span>
</div>
</div>
</div>
<div class="card mb-2">
<div class="card-body">
<h5 class="card-title">When do you want to execute the trade?</h5>
<div class="form-group">
<label asp-for="StartOn" class="control-label"></label>
<div class="input-group">
<input type="datetime-local" asp-for="StartOn"
value="@(Model.StartOn?.ToString("u", CultureInfo.InvariantCulture))"
class="form-control flatdtpicker" placeholder="Start from"/>
<div class="input-group-append">
<button class="btn btn-secondary input-group-clear" type="button" title="Clear">
&times;
</button>
</div>
</div>
<span asp-validation-for="StartOn" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TriggerEveryAmount" class="control-label"></label>
<div class="input-group">
<input type="number" asp-for="TriggerEveryAmount" placeholder="Amount" class="form-control">
<select class="custom-select" asp-for="TriggerEvery" asp-items="@Html.GetEnumSelectList(typeof(TimerTriggerParameters.TimerResetEvery))">
</select>
</div>
<span asp-validation-for="TriggerEveryAmount" class="text-danger"></span>
<span asp-validation-for="TriggerEvery" class="text-danger"></span>
</div>
</div>
</div>
<div class="card mb-2">
<div class="card-body">
<h5 class="card-title">Set up your trading conditions</h5>
<div class="form-group">
<label asp-for="MarketSymbol" class="control-label"></label>
<input asp-for="MarketSymbol" class="form-control autocomplete" data-datasrc="availableMarketSymbols" placeholder="Start typing to see a list of available trading pairs"/>
<span asp-validation-for="MarketSymbol" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="IsBuy" class="control-label"></label>
<input type="checkbox" asp-for="IsBuy" class="form-check"/>
<span asp-validation-for="IsBuy" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FiatAmount" class="control-label"></label>
<input type="number" step="any" asp-for="FiatAmount" class="form-control"/>
<span asp-validation-for="FiatAmount" class="text-danger"></span>
</div>
</div>
</div>
<div class="mt-2">
<button type="submit" class="btn btn-primary">Save</button>
<a asp-action="GetServices" asp-controller="ExternalServices" class="btn btn-secondary">Back to recipe</a>
</div>
</form>
<script>
var actionUrlMapping = @Json.Serialize(Model.ExchangeServices.ToDictionary(item => item.Value, item => @Url.Action("GetAvailableMarketSymbols", "PlaceOrder", new {ExternalServiceId = item.Value})));;
var availableMarketSymbols = [];
$(document).ready(function(){
$("#SelectedExchangeServiceId").on("input", populateAvailableMarketSymbols);
function populateAvailableMarketSymbols(){
var value = $("#SelectedExchangeServiceId").val();
if(!value){
availableMarketSymbols = [];
}else{
$.ajax({
url: actionUrlMapping[value],
success: function(response){
availableMarketSymbols = response;
},
error: function(){
availableMarketSymbols = [];
}
});
}
}
populateAvailableMarketSymbols();
})
</script>

View File

@ -1,6 +1,6 @@
@if (User.Claims.Any())
{
<li class="nav-item">
<a asp-controller="Presets" asp-action="ChoosePreset" class="nav-link">Presets</a>
<li class="nav-item mr-3">
<a asp-controller="Presets" asp-action="ChoosePreset">Presets</a>
</li>
}
}

View File

@ -10,9 +10,9 @@
<ItemGroup>
<PackageReference Include="MaxKagamine.Moq.HttpClient" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -10,9 +10,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -11,9 +11,9 @@
<ItemGroup>
<PackageReference Include="MaxKagamine.Moq.HttpClient" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -8,6 +8,6 @@
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.1" />
</ItemGroup>
</Project>

View File

@ -26,7 +26,6 @@ namespace BtcTransmuter.Extension.Webhook.Triggers.ReceiveWebRequest
public static readonly List<string> AllowedMethods = new List<string>()
{
"",
HttpMethod.Get.ToString(),
HttpMethod.Put.ToString(),
HttpMethod.Head.ToString(),
@ -75,7 +74,6 @@ namespace BtcTransmuter.Extension.Webhook.Triggers.ReceiveWebRequest
}
[Route("trigger/{relativeUrl?}")]
[AllowAnonymous]
public async Task<IActionResult> Trigger(string relativeUrl)
{
string body = null;

View File

@ -1,4 +1,4 @@
using Newtonsoft.Json.Linq;
using System.Net.Http;
namespace BtcTransmuter.Extension.Webhook.Triggers.ReceiveWebRequest
{
@ -8,7 +8,7 @@ namespace BtcTransmuter.Extension.Webhook.Triggers.ReceiveWebRequest
public string RelativeUrl { get; set; } = "";
public string Body { get; set; }= "";
public JObject BodyJson { get; set; }
public dynamic BodyJson { get; set; }
}
}

View File

@ -22,7 +22,7 @@ namespace BtcTransmuter.Extension.Webhook.Triggers.ReceiveWebRequest
ReceiveWebRequestTriggerData triggerData,
ReceiveWebRequestTriggerParameters parameters)
{
if (!string.IsNullOrEmpty(parameters.Method) && triggerData.Method != parameters.Method)
if (triggerData.Method != parameters.Method)
{
return Task.FromResult(false);
}

View File

@ -6,19 +6,13 @@
}
<div>
Receive a
@if (!string.IsNullOrEmpty(data.Method))
{
<kbd>@data.Method</kbd>
}
HTTP request at
Receive a <kbd>@data.Method</kbd> HTTP request at
<kbd>
@Url.Action("Trigger", "ReceiveWebRequest", new
{
relativeUrl = data.RelativeUrl
}, Context.Request.Scheme)
</kbd>
@if (!string.IsNullOrEmpty(data.Body))
}, Context.Request.Scheme)</kbd>
@if (!string.IsNullOrEmpty(@data.Body))
{
<span> with a body <kbd>@Enum.GetName(typeof(ReceiveWebRequestTriggerParameters.FieldComparer), data.BodyComparer) </kbd>@data.Body</span>
}

View File

@ -26,7 +26,8 @@ namespace BtcTransmuter.Data
public DbSet<RecipeInvocation> RecipeInvocations { get; set; }
public DbSet<RecipeTrigger> RecipeTriggers { get; set; }
public DbSet<RecipeAction> RecipeActions { get; set; }
public DbSet<Settings> Settings { get; set; }
public DbSet<Settings> Settings { get; set; }
public DbSet<U2FDevice> U2FDevices { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
@ -78,4 +79,4 @@ namespace BtcTransmuter.Data
return new ApplicationDbContext(optionsBuilder.Options, null, null);
}
}
}
}

View File

@ -5,7 +5,10 @@
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj"/>
<ProjectReference Include="..\BtcTransmuter.Data\BtcTransmuter.Data.csproj"/>
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
<ProjectReference Include="..\BtcTransmuter.Data\BtcTransmuter.Data.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="U2F.Core" Version="2.0.1" />
</ItemGroup>
</Project>

View File

@ -1,505 +0,0 @@
// <auto-generated />
using System;
using BtcTransmuter.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace BtcTransmuter.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20200402084229_AddUserBlob")]
partial class AddUserBlob
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.1");
modelBuilder.Entity("BtcTransmuter.Data.Entities.ExternalServiceData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("DataJson")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Type")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("ExternalServices");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.Recipe", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<bool>("Enabled")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Recipes");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeAction", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("ActionId")
.HasColumnType("TEXT");
b.Property<string>("DataJson")
.HasColumnType("TEXT");
b.Property<string>("ExternalServiceId")
.HasColumnType("TEXT");
b.Property<int>("Order")
.HasColumnType("INTEGER");
b.Property<string>("RecipeActionGroupId")
.HasColumnType("TEXT");
b.Property<string>("RecipeId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ExternalServiceId");
b.HasIndex("RecipeActionGroupId");
b.HasIndex("RecipeId");
b.ToTable("RecipeActions");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeActionGroup", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("RecipeId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RecipeId");
b.ToTable("RecipeActionGroups");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeInvocation", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("ActionResult")
.HasColumnType("TEXT");
b.Property<string>("RecipeAction")
.HasColumnType("TEXT");
b.Property<string>("RecipeId")
.HasColumnType("TEXT");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.Property<string>("TriggerDataJson")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RecipeId");
b.ToTable("RecipeInvocations");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeTrigger", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("DataJson")
.HasColumnType("TEXT");
b.Property<string>("ExternalServiceId")
.HasColumnType("TEXT");
b.Property<string>("RecipeId")
.HasColumnType("TEXT");
b.Property<string>("TriggerId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ExternalServiceId");
b.HasIndex("RecipeId")
.IsUnique();
b.ToTable("RecipeTriggers");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.Settings", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("DataJson")
.HasColumnType("TEXT");
b.Property<string>("Key")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Key")
.IsUnique();
b.ToTable("Settings");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.User", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("DataJson")
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.ExternalServiceData", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.User", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.Recipe", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.User", null)
.WithMany("Recipes")
.HasForeignKey("UserId");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeAction", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.ExternalServiceData", "ExternalService")
.WithMany("RecipeActions")
.HasForeignKey("ExternalServiceId");
b.HasOne("BtcTransmuter.Data.Entities.RecipeActionGroup", "RecipeActionGroup")
.WithMany("RecipeActions")
.HasForeignKey("RecipeActionGroupId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BtcTransmuter.Data.Entities.Recipe", "Recipe")
.WithMany("RecipeActions")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeActionGroup", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.Recipe", "Recipe")
.WithMany("RecipeActionGroups")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeInvocation", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.Recipe", "Recipe")
.WithMany("RecipeInvocations")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeTrigger", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.ExternalServiceData", "ExternalService")
.WithMany("RecipeTriggers")
.HasForeignKey("ExternalServiceId");
b.HasOne("BtcTransmuter.Data.Entities.Recipe", "Recipe")
.WithOne("RecipeTrigger")
.HasForeignKey("BtcTransmuter.Data.Entities.RecipeTrigger", "RecipeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BtcTransmuter.Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,22 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace BtcTransmuter.Data.Migrations
{
public partial class AddUserBlob : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "DataJson",
table: "AspNetUsers",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DataJson",
table: "AspNetUsers");
}
}
}

View File

@ -14,25 +14,20 @@ namespace BtcTransmuter.Data.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.1");
.HasAnnotation("ProductVersion", "2.2.4-servicing-10062");
modelBuilder.Entity("BtcTransmuter.Data.Entities.ExternalServiceData", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
.ValueGeneratedOnAdd();
b.Property<string>("DataJson")
.HasColumnType("TEXT");
b.Property<string>("DataJson");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Name");
b.Property<string>("Type")
.HasColumnType("TEXT");
b.Property<string>("Type");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("UserId");
b.HasKey("Id");
@ -44,20 +39,15 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("BtcTransmuter.Data.Entities.Recipe", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
.ValueGeneratedOnAdd();
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<string>("Description");
b.Property<bool>("Enabled")
.HasColumnType("INTEGER");
b.Property<bool>("Enabled");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Name");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("UserId");
b.HasKey("Id");
@ -69,26 +59,19 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeAction", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
.ValueGeneratedOnAdd();
b.Property<string>("ActionId")
.HasColumnType("TEXT");
b.Property<string>("ActionId");
b.Property<string>("DataJson")
.HasColumnType("TEXT");
b.Property<string>("DataJson");
b.Property<string>("ExternalServiceId")
.HasColumnType("TEXT");
b.Property<string>("ExternalServiceId");
b.Property<int>("Order")
.HasColumnType("INTEGER");
b.Property<int>("Order");
b.Property<string>("RecipeActionGroupId")
.HasColumnType("TEXT");
b.Property<string>("RecipeActionGroupId");
b.Property<string>("RecipeId")
.HasColumnType("TEXT");
b.Property<string>("RecipeId");
b.HasKey("Id");
@ -104,11 +87,9 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeActionGroup", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
.ValueGeneratedOnAdd();
b.Property<string>("RecipeId")
.HasColumnType("TEXT");
b.Property<string>("RecipeId");
b.HasKey("Id");
@ -120,23 +101,17 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeInvocation", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
.ValueGeneratedOnAdd();
b.Property<string>("ActionResult")
.HasColumnType("TEXT");
b.Property<string>("ActionResult");
b.Property<string>("RecipeAction")
.HasColumnType("TEXT");
b.Property<string>("RecipeAction");
b.Property<string>("RecipeId")
.HasColumnType("TEXT");
b.Property<string>("RecipeId");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.Property<DateTime>("Timestamp");
b.Property<string>("TriggerDataJson")
.HasColumnType("TEXT");
b.Property<string>("TriggerDataJson");
b.HasKey("Id");
@ -148,20 +123,15 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeTrigger", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
.ValueGeneratedOnAdd();
b.Property<string>("DataJson")
.HasColumnType("TEXT");
b.Property<string>("DataJson");
b.Property<string>("ExternalServiceId")
.HasColumnType("TEXT");
b.Property<string>("ExternalServiceId");
b.Property<string>("RecipeId")
.HasColumnType("TEXT");
b.Property<string>("RecipeId");
b.Property<string>("TriggerId")
.HasColumnType("TEXT");
b.Property<string>("TriggerId");
b.HasKey("Id");
@ -176,14 +146,11 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("BtcTransmuter.Data.Entities.Settings", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
.ValueGeneratedOnAdd();
b.Property<string>("DataJson")
.HasColumnType("TEXT");
b.Property<string>("DataJson");
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<string>("Key");
b.HasKey("Id");
@ -196,56 +163,39 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("BtcTransmuter.Data.Entities.User", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("DataJson")
.HasColumnType("TEXT");
.IsConcurrencyToken();
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("EmailConfirmed");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
@ -263,18 +213,15 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
.IsConcurrencyToken();
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
@ -289,18 +236,14 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
.IsRequired();
b.HasKey("Id");
@ -312,18 +255,14 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
.IsRequired();
b.HasKey("Id");
@ -334,18 +273,14 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
@ -356,11 +291,9 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("UserId");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
@ -371,17 +304,13 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("UserId");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Name");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
@ -397,7 +326,7 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("BtcTransmuter.Data.Entities.Recipe", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.User", null)
b.HasOne("BtcTransmuter.Data.Entities.User")
.WithMany("Recipes")
.HasForeignKey("UserId");
});
@ -449,53 +378,47 @@ namespace BtcTransmuter.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.User", null)
b.HasOne("BtcTransmuter.Data.Entities.User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.User", null)
b.HasOne("BtcTransmuter.Data.Entities.User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BtcTransmuter.Data.Entities.User", null)
b.HasOne("BtcTransmuter.Data.Entities.User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.User", null)
b.HasOne("BtcTransmuter.Data.Entities.User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}

View File

@ -70,30 +70,34 @@ namespace BtcTransmuter.Services
public async Task<IEnumerable<RecipeInvocation>> GetRecipeInvocations(RecipeInvocationsQuery query)
{
using var scope = _serviceScopeFactory.CreateScope();
await using var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var queryable = context.RecipeInvocations
.Include(invocation => invocation.Recipe)
.ThenInclude(recipe => recipe.RecipeTrigger)
.AsEnumerable();
if (query.OrderBy != null)
using (var scope = _serviceScopeFactory.CreateScope())
{
switch (query.OrderBy.Field)
using (var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
case RecipeInvocationsQuery.RecipeInvocationsQueryOrderBy.Timestamp:
queryable = query.OrderBy.Direction == OrderDirection.Ascending
? queryable.OrderBy(invocation => invocation.Timestamp)
: queryable.OrderByDescending(invocation => invocation.Timestamp);
break;
default:
throw new ArgumentOutOfRangeException();
var queryable = context.RecipeInvocations
.Include(invocation => invocation.Recipe)
.ThenInclude(recipe => recipe.RecipeTrigger)
.AsEnumerable();
if (query.OrderBy != null)
{
switch (query.OrderBy.Field)
{
case RecipeInvocationsQuery.RecipeInvocationsQueryOrderBy.Timestamp:
queryable = query.OrderBy.Direction == OrderDirection.Ascending
? queryable.OrderBy(invocation => invocation.Timestamp)
: queryable.OrderByDescending(invocation => invocation.Timestamp);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
return queryable
.Where(invocation =>
invocation.RecipeId.Equals(query.RecipeId, StringComparison.InvariantCultureIgnoreCase))
.Skip(query.Skip).Take(query.Take).ToList();
}
}
return queryable
.Where(invocation =>
invocation.RecipeId.Equals(query.RecipeId, StringComparison.InvariantCultureIgnoreCase))
.Skip(query.Skip).Take(query.Take).ToList();
}
public async Task AddOrUpdateRecipe(Recipe recipe)
@ -146,8 +150,6 @@ namespace BtcTransmuter.Services
public async Task AddOrUpdateRecipeAction(RecipeAction action)
{
var oldES = action.ExternalService;
action.ExternalService = null;
using (var scope = _serviceScopeFactory.CreateScope())
{
using (var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
@ -158,12 +160,10 @@ namespace BtcTransmuter.Services
}
else
{
context.Entry(action).State = EntityState.Modified;
}
await context.SaveChangesAsync();
action.ExternalService = oldES;
}
}
}

View File

@ -3,6 +3,7 @@ using BtcTransmuter.Abstractions.ExternalServices;
using BtcTransmuter.Abstractions.Recipes;
using BtcTransmuter.Abstractions.Settings;
using BtcTransmuter.Abstractions.Triggers;
using BtcTransmuter.Abstractions.U2F;
using Microsoft.Extensions.DependencyInjection;
namespace BtcTransmuter.Services
@ -16,6 +17,7 @@ namespace BtcTransmuter.Services
collection.AddSingleton<IActionDispatcher, ActionDispatcher>();
collection.AddSingleton<ITriggerDispatcher, TriggerDispatcher>();
collection.AddSingleton<ISettingsManager, SettingsManager>();
collection.AddSingleton<IU2FService, U2FService>();
}
}
}
}

View File

@ -0,0 +1,273 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.Extensions;
using BtcTransmuter.Abstractions.U2F;
using BtcTransmuter.Data;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Entities.U2F;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using U2F.Core.Exceptions;
using U2F.Core.Models;
using U2F.Core.Utils;
namespace BtcTransmuter.Services
{
public class U2FService : IU2FService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public U2FService(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
private ConcurrentDictionary<string, List<U2FDeviceAuthenticationRequest>> UserAuthenticationRequests
{
get;
set;
}
= new ConcurrentDictionary<string, List<U2FDeviceAuthenticationRequest>>();
public async Task<List<U2FDevice>> GetDevices(string userId)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
using (var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
return await context.U2FDevices
.Where(device => device.UserId == userId)
.ToListAsync();
}
}
}
public async Task RemoveDevice(string id, string userId)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
using (var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
var device = await context.U2FDevices.FindAsync(id);
if (device == null || !device.UserId.Equals(userId, StringComparison.InvariantCulture))
{
return;
}
context.U2FDevices.Remove(device);
await context.SaveChangesAsync();
}
}
}
public async Task<bool> HasDevices(string userId)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
using (var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
return await context.U2FDevices.Where(fDevice => fDevice.UserId == userId).AnyAsync();
}
}
}
public ServerRegisterResponse StartDeviceRegistration(string userId, string appId)
{
var startedRegistration = StartDeviceRegistrationCore(appId);
UserAuthenticationRequests.AddOrReplace(userId, new List<U2FDeviceAuthenticationRequest>()
{
new U2FDeviceAuthenticationRequest()
{
AppId = startedRegistration.AppId,
Challenge = startedRegistration.Challenge,
Version = global::U2F.Core.Crypto.U2F.U2FVersion,
}
});
return new ServerRegisterResponse
{
AppId = startedRegistration.AppId,
Challenge = startedRegistration.Challenge,
Version = startedRegistration.Version
};
}
public async Task<bool> CompleteRegistration(string userId, string deviceResponse, string name)
{
if (string.IsNullOrWhiteSpace(deviceResponse))
return false;
if (!UserAuthenticationRequests.ContainsKey(userId) || !UserAuthenticationRequests[userId].Any())
{
return false;
}
var registerResponse = RegisterResponse.FromJson<RegisterResponse>(deviceResponse);
//There is only 1 request when registering device
var authenticationRequest = UserAuthenticationRequests[userId].First();
var startedRegistration =
new StartedRegistration(authenticationRequest.Challenge, authenticationRequest.AppId);
var registration = FinishRegistrationCore(startedRegistration, registerResponse);
UserAuthenticationRequests.AddOrReplace(userId, new List<U2FDeviceAuthenticationRequest>());
using (var scope = _serviceScopeFactory.CreateScope())
{
using (var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
var duplicate = context.U2FDevices.Any(device =>
device.UserId == userId &&
device.KeyHandle.Equals(registration.KeyHandle) &&
device.PublicKey.Equals(registration.PublicKey));
if (duplicate)
{
throw new U2fException("The U2F Device has already been registered with this user");
}
await context.U2FDevices.AddAsync(new U2FDevice()
{
Id = Guid.NewGuid().ToString(),
AttestationCert = registration.AttestationCert,
Counter = Convert.ToInt32(registration.Counter),
Name = name,
KeyHandle = registration.KeyHandle,
PublicKey = registration.PublicKey,
UserId = userId
});
await context.SaveChangesAsync();
}
}
return true;
}
public async Task<bool> AuthenticateUser(string userId, string deviceResponse)
{
if (string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(deviceResponse))
return false;
var authenticateResponse =
AuthenticateResponse.FromJson<AuthenticateResponse>(deviceResponse);
using (var scope = _serviceScopeFactory.CreateScope())
{
using (var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
var keyHandle = authenticateResponse.KeyHandle.Base64StringToByteArray();
var device = await context.U2FDevices.Where(fDevice =>
fDevice.UserId == userId &&
fDevice.KeyHandle == keyHandle).SingleOrDefaultAsync();
if (device == null)
return false;
// User will have a authentication request for each device they have registered so get the one that matches the device key handle
var authenticationRequest =
UserAuthenticationRequests[userId].First(f =>
f.KeyHandle.Equals(authenticateResponse.KeyHandle, StringComparison.InvariantCulture));
var registration = new DeviceRegistration(device.KeyHandle, device.PublicKey,
device.AttestationCert, Convert.ToUInt32(device.Counter));
var authentication = new StartedAuthentication(authenticationRequest.Challenge,
authenticationRequest.AppId, authenticationRequest.KeyHandle);
var challengeAuthenticationRequestMatch = UserAuthenticationRequests[userId].First(f =>
f.Challenge.Equals(authenticateResponse.GetClientData().Challenge,
StringComparison.InvariantCulture));
if (authentication.Challenge != challengeAuthenticationRequestMatch.Challenge)
{
authentication = new StartedAuthentication(challengeAuthenticationRequestMatch.Challenge,
authenticationRequest.AppId, authenticationRequest.KeyHandle);
}
FinishAuthenticationCore(authentication, authenticateResponse, registration);
UserAuthenticationRequests.AddOrReplace(userId, new List<U2FDeviceAuthenticationRequest>());
device.Counter = Convert.ToInt32(registration.Counter);
await context.SaveChangesAsync();
}
}
return true;
}
public async Task<List<ServerChallenge>> GenerateDeviceChallenges(string userId, string appId)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
using (var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
var devices = await context.U2FDevices.Where(fDevice => fDevice.UserId == userId)
.ToListAsync();
if (devices.Count == 0)
return null;
var requests = new List<U2FDeviceAuthenticationRequest>();
var serverChallenges = new List<ServerChallenge>();
foreach (var registeredDevice in devices)
{
var challenge = StartAuthenticationCore(appId, registeredDevice);
serverChallenges.Add(new ServerChallenge()
{
challenge = challenge.Challenge,
appId = challenge.AppId,
version = challenge.Version,
keyHandle = challenge.KeyHandle
});
requests.Add(
new U2FDeviceAuthenticationRequest()
{
AppId = appId,
Challenge = challenge.Challenge,
KeyHandle = registeredDevice.KeyHandle.ByteArrayToBase64String(),
Version = global::U2F.Core.Crypto.U2F.U2FVersion
});
}
UserAuthenticationRequests.AddOrReplace(userId, requests);
return serverChallenges;
}
}
}
protected virtual StartedRegistration StartDeviceRegistrationCore(string appId)
{
return global::U2F.Core.Crypto.U2F.StartRegistration(appId);
}
protected virtual DeviceRegistration FinishRegistrationCore(StartedRegistration startedRegistration,
RegisterResponse registerResponse)
{
return global::U2F.Core.Crypto.U2F.FinishRegistration(startedRegistration, registerResponse);
}
protected virtual StartedAuthentication StartAuthenticationCore(string appId, U2FDevice registeredDevice)
{
return global::U2F.Core.Crypto.U2F.StartAuthentication(appId,
new DeviceRegistration(registeredDevice.KeyHandle, registeredDevice.PublicKey,
registeredDevice.AttestationCert, (uint) registeredDevice.Counter));
}
protected virtual void FinishAuthenticationCore(StartedAuthentication authentication,
AuthenticateResponse authenticateResponse, DeviceRegistration registration)
{
global::U2F.Core.Crypto.U2F.FinishAuthentication(authentication, authenticateResponse, registration);
}
}
}

View File

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -9,8 +9,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

View File

@ -9,7 +9,7 @@ namespace BtcTransmuter.Tests
public class InterpolationTests
{
[Fact]
public void CanInterpolate()
public async Task CanInterpolate()
{
Assert.Equal("hello world",InterpolationHelper.InterpolateString("{{Data1 +\" \" + Data2}}", new Dictionary<string, object>()
{

View File

@ -15,7 +15,8 @@ namespace BtcTransmuter.Areas.Identity
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => { services.AddScoped<BTCPayAuthService>(); });
builder.ConfigureServices((context, services) => {
});
}
}
}

View File

@ -8,7 +8,6 @@
}
@inject ISettingsManager SettingsManager
@inject BtcTransmuterOptions Options;
@{
var settings = await SettingsManager.GetSettings<SystemSettings>(nameof(SystemSettings));
}
@ -18,45 +17,37 @@
<div class="col-md-4">
<section>
<form id="account" method="post">
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control"/>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control"/>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="Input.RememberMe">
<input asp-for="Input.RememberMe"/>
<input asp-for="Input.RememberMe" />
@Html.DisplayNameFor(m => m.Input.RememberMe)
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">
Log in
@if (Options.DisableInternalAuth)
{
<span> with BTCPay Server account</span>
}
else if (Options.BTCPayAuthServer != null)
{
<span> (local account or BTCPay Server account)</span>
}
</button>
<button type="submit" class="btn btn-primary">Log in</button>
</div>
@if (!settings.DisableRegistration && !Options.DisableInternalAuth)
@if (!settings.DisableRegistration)
{
<div class="form-group">
@* <p> *@
@* <a id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a> *@
@* </p> *@
<p>
<a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
</p>
@ -65,8 +56,37 @@
</form>
</section>
</div>
@* <div class="col-md-6 col-md-offset-2"> *@
@* <section> *@
@* <h4>Use another service to log in.</h4> *@
@* <hr /> *@
@* @{ *@
@* if ((Model.ExternalLogins?.Count ?? 0) == 0) *@
@* { *@
@* <div> *@
@* <p> *@
@* Coming soon: Log in with Btcpayserver account *@
@* </p> *@
@* </div> *@
@* } *@
@* else *@
@* { *@
@* <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal"> *@
@* <div> *@
@* <p> *@
@* @foreach (var provider in Model.ExternalLogins) *@
@* { *@
@* <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> *@
@* } *@
@* </p> *@
@* </div> *@
@* </form> *@
@* } *@
@* } *@
@* </section> *@
@* </div> *@
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial"/>
}
<partial name="_ValidationScriptsPartial" />
}

View File

@ -2,41 +2,27 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BtcTransmuter.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class LoginModel : PageModel
{
private readonly BTCPayAuthService _btcPayAuthService;
private readonly SignInManager<User> _signInManager;
private readonly ILogger<LoginModel> _logger;
private readonly IBtcTransmuterOptions _btcTransmuterOptions;
private readonly IHttpClientFactory _httpClientFactory;
private readonly UserManager<User> _userManager;
public LoginModel(BTCPayAuthService btcPayAuthService,SignInManager<User> signInManager, ILogger<LoginModel> logger, IBtcTransmuterOptions btcTransmuterOptions, IHttpClientFactory httpClientFactory, UserManager<User> userManager)
public LoginModel(SignInManager<User> signInManager, ILogger<LoginModel> logger)
{
_btcPayAuthService = btcPayAuthService;
_signInManager = signInManager;
_logger = logger;
_btcTransmuterOptions = btcTransmuterOptions;
_httpClientFactory = httpClientFactory;
_userManager = userManager;
}
[BindProperty]
@ -86,66 +72,32 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account
if (ModelState.IsValid)
{
var user = await _btcPayAuthService.LoginAndRegisterIfNeeded(Input.Email, Input.Password);
if (user != null)
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, Input.RememberMe);
_logger.LogInformation("User logged in using BTCPay.");
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
else if (!_btcTransmuterOptions.DisableInternalAuth)
if (result.RequiresTwoFactor)
{
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe,
lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa",
new {ReturnUrl = returnUrl, RememberMe = Input.RememberMe});
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
}
// If we got this far, something failed, redisplay form
return Page();
}
}
public class GetCurrentUserResponse {
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("emailConfirmed")]
public bool EmailConfirmed { get; set; }
[JsonProperty("requiresEmailConfirmation")]
public bool RequiresEmailConfirmation { get; set; }
[JsonProperty("roles")]
public string[] Roles { get; set; }
public override string ToString()
{
return $"{Id}{Email}";
}
}
}

View File

@ -1,27 +0,0 @@
@page
@model BTCPayAccountLinkModel
@{
ViewData["Title"] = "BTCPay Account Link";
ViewData["ActivePage"] = ManageNavPages.BTCPayAccountLink;
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="change-password-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.AccessToken"></label>
<input asp-for="Input.AccessToken" class="form-control" />
<span asp-validation-for="Input.AccessToken" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Update</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@ -1,109 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
{
public class BTCPayAccountLinkModel : PageModel
{
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
private readonly IBtcTransmuterOptions _btcTransmuterOptions;
private readonly BTCPayAuthService _btcPayAuthService;
public BTCPayAccountLinkModel(
UserManager<User> userManager,
SignInManager<User> signInManager,
IBtcTransmuterOptions btcTransmuterOptions, BTCPayAuthService btcPayAuthService)
{
_userManager = userManager;
_signInManager = signInManager;
_btcTransmuterOptions = btcTransmuterOptions;
_btcPayAuthService = btcPayAuthService;
}
public bool CurrentTokenValid { get; set; }
[BindProperty] public InputModel Input { get; set; }
[TempData] public string StatusMessage { get; set; }
public class InputModel
{
[Display(Name = "Access Token")] public string AccessToken { get; set; }
}
public async Task<IActionResult> OnGetAsync()
{
if (_btcTransmuterOptions.BTCPayAuthServer == null)
{
return RedirectToPage("./ChangePassword");
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var blob = user.Get<UserBlob>();
Input = new InputModel()
{
AccessToken = blob.BTCPayAuthDetails.AccessToken
};
if (!string.IsNullOrEmpty(Input.AccessToken))
{
var currentToken = await _btcPayAuthService.CheckToken(user);
CurrentTokenValid = currentToken != null && currentToken.Id == blob.BTCPayAuthDetails.UserId;
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (_btcTransmuterOptions.BTCPayAuthServer == null)
{
return RedirectToPage("./ChangePassword");
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var blob = user.Get<UserBlob>();
if (_btcTransmuterOptions.DisableInternalAuth && string.IsNullOrEmpty(Input.AccessToken))
{
ModelState.AddModelError("Input.AccessToken", "Access token is required.");
}
else if (_btcTransmuterOptions.DisableInternalAuth && !string.IsNullOrEmpty(Input.AccessToken))
{
var response = await _btcPayAuthService.CheckToken(user);
CurrentTokenValid = response != null;
if (CurrentTokenValid)
{
blob.BTCPayAuthDetails.AccessToken = Input.AccessToken;
blob.BTCPayAuthDetails.UserId = response.Id;
user.Set(blob);
await _userManager.UpdateAsync(user);
}
else if (!CurrentTokenValid && _btcTransmuterOptions.DisableInternalAuth)
{
ModelState.AddModelError("Input.AccessToken", "Invalid Access token.");
}
}
if (!ModelState.IsValid)
{
return Page();
}
return RedirectToPage();
}
}
}

View File

@ -1,11 +1,13 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using BtcTransmuter.Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
{
public class ChangePasswordModel : PageModel
@ -13,22 +15,22 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
private readonly ILogger<ChangePasswordModel> _logger;
private readonly IBtcTransmuterOptions _btcTransmuterOptions;
public ChangePasswordModel(
UserManager<User> userManager,
SignInManager<User> signInManager,
ILogger<ChangePasswordModel> logger, IBtcTransmuterOptions btcTransmuterOptions)
ILogger<ChangePasswordModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_btcTransmuterOptions = btcTransmuterOptions;
}
[BindProperty] public InputModel Input { get; set; }
[BindProperty]
public InputModel Input { get; set; }
[TempData] public string StatusMessage { get; set; }
[TempData]
public string StatusMessage { get; set; }
public class InputModel
{
@ -38,8 +40,7 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
public string OldPassword { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.",
MinimumLength = 6)]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
@ -52,11 +53,6 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
public async Task<IActionResult> OnGetAsync()
{
if (_btcTransmuterOptions.DisableInternalAuth)
{
return RedirectToPage("./BTCPayAccountLink");
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
@ -74,11 +70,6 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
public async Task<IActionResult> OnPostAsync()
{
if (_btcTransmuterOptions.DisableInternalAuth)
{
return RedirectToPage("./BTCPayAccountLink");
}
if (!ModelState.IsValid)
{
return Page();
@ -90,15 +81,13 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var changePasswordResult =
await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
if (!changePasswordResult.Succeeded)
{
foreach (var error in changePasswordResult.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
@ -109,4 +98,4 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
return RedirectToPage();
}
}
}
}

View File

@ -0,0 +1,12 @@
@page
@model DownloadPersonalDataModel
@{
ViewData["Title"] = "Download Your Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}
<h4>@ViewData["Title"]</h4>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BtcTransmuter.Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
{
public class DownloadPersonalDataModel : PageModel
{
private readonly UserManager<User> _userManager;
private readonly ILogger<DownloadPersonalDataModel> _logger;
public DownloadPersonalDataModel(
UserManager<User> userManager,
ILogger<DownloadPersonalDataModel> logger)
{
_userManager = userManager;
_logger = logger;
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
_logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User));
// Only include personal data for download
var personalData = new Dictionary<string, string>();
var personalDataProps = typeof(User).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
foreach (var p in personalDataProps)
{
personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
}
Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json");
return new FileContentResult(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(personalData)), "text/json");
}
}
}

View File

@ -6,24 +6,35 @@
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage"/>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="profile-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Username"></label>
<input asp-for="Username" class="form-control" disabled/>
<input asp-for="Username" class="form-control" disabled />
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control"/>
@if (Model.IsEmailConfirmed)
{
<div class="input-group">
<input asp-for="Input.Email" class="form-control" />
<span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok text-success"></span></span>
</div>
}
else
{
<input asp-for="Input.Email" class="form-control" />
@* <button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button> *@
}
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.AllowBasicAuth"></label>
<input asp-for="Input.AllowBasicAuth" class="form-check"/>
<span asp-validation-for="Input.AllowBasicAuth" class="text-danger"></span>
<label asp-for="Input.PhoneNumber"></label>
<input asp-for="Input.PhoneNumber" class="form-control" />
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
<button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
</form>
@ -31,5 +42,5 @@
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial"/>
<partial name="_ValidationScriptsPartial" />
}

View File

@ -5,7 +5,6 @@ using System.Linq;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
@ -17,27 +16,37 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
{
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
private readonly IEmailSender _emailSender;
public IndexModel(
UserManager<User> userManager,
SignInManager<User> signInManager)
SignInManager<User> signInManager,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
}
public string Username { get; set; }
[TempData] public string StatusMessage { get; set; }
public bool IsEmailConfirmed { get; set; }
[BindProperty] public InputModel Input { get; set; }
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required] [EmailAddress] public string Email { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Display(Name = "Allow Basic Auth using this account")]
public bool AllowBasicAuth { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
public async Task<IActionResult> OnGetAsync()
@ -50,15 +59,18 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
var userName = await _userManager.GetUserNameAsync(user);
var email = await _userManager.GetEmailAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
Username = userName;
Input = new InputModel
{
Email = email,
AllowBasicAuth = user.Get<UserBlob>().BasicAuth
PhoneNumber = phoneNumber
};
IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
return Page();
}
@ -70,7 +82,6 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
}
var user = await _userManager.GetUserAsync(User);
var blob = user.Get<UserBlob>();
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
@ -83,27 +94,18 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException(
$"Unexpected error occurred setting email for user with ID '{userId}'.");
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
}
var blobChanged = false;
if (Input.AllowBasicAuth != blob.BasicAuth)
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
if (Input.PhoneNumber != phoneNumber)
{
blob.BasicAuth = Input.AllowBasicAuth;
blobChanged = true;
}
if (blobChanged)
{
user.Set(blob);
var updated = await _userManager.UpdateAsync(user);
if (!updated.Succeeded)
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException(
$"Unexpected error occurred setting data for user with ID '{userId}'.");
throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{userId}'.");
}
}
@ -111,5 +113,36 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
public async Task<IActionResult> OnPostSendVerificationEmailAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var userId = await _userManager.GetUserIdAsync(user);
var email = await _userManager.GetEmailAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
StatusMessage = "Verification email sent. Please check your email.";
return RedirectToPage();
}
}
}
}

View File

@ -8,7 +8,6 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
public static string Index => "Index";
public static string ChangePassword => "ChangePassword";
public static string BTCPayAccountLink => "BTCPayAccountLink";
public static string ExternalLogins => "ExternalLogins";
@ -25,7 +24,6 @@ namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
public static string BTCPayAccountLinkNavClass(ViewContext viewContext) => PageNavClass(viewContext, BTCPayAccountLink);
private static string PageNavClass(ViewContext viewContext, string page)
{

View File

@ -13,6 +13,9 @@
<p>
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
</p>
<form id="download-data" asp-page="DownloadPersonalData" method="post" class="form-group">
<button class="btn btn-primary" type="submit">Download</button>
</form>
<p>
<a id="delete" asp-page="DeletePersonalData" class="btn btn-primary">Delete</a>
</p>

View File

@ -3,17 +3,21 @@ using BtcTransmuter.Data.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace BtcTransmuter.Areas.Identity.Pages.Account.Manage
{
public class PersonalDataModel : PageModel
{
private readonly UserManager<User> _userManager;
private readonly ILogger<PersonalDataModel> _logger;
public PersonalDataModel(
UserManager<User> userManager)
UserManager<User> userManager,
ILogger<PersonalDataModel> logger)
{
_userManager = userManager;
_logger = logger;
}
public async Task<IActionResult> OnGet()

View File

@ -1,22 +1,12 @@
@using BtcTransmuter.Data.Entities
@using Microsoft.AspNetCore.Identity
@inject SignInManager<User> SignInManager
@inject IBtcTransmuterOptions BtcTransmuterOptions;
@{
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<ul class="nav nav-pills flex-column">
<li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li>
@if (!BtcTransmuterOptions.DisableInternalAuth)
{
<li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li>
}
@if (BtcTransmuterOptions.BTCPayAuthServer != null)
{
<li class="nav-item"><a class="nav-link @ManageNavPages.BTCPayAccountLinkNavClass(ViewContext)" id="btcpay-account-link" asp-page="./BTCPayAccountLink">BTCPay account link</a></li>
}
<li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li>
@if (hasExternalLogins)
{
<li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li>

View File

@ -4,11 +4,13 @@
ViewData["Title"] = "Register";
}
<h1>Create a new account</h1>
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>

View File

@ -1,15 +0,0 @@
using System.Net;
using Microsoft.AspNetCore.Authentication;
namespace BtcTransmuter.Auth
{
public static class BasicAuthenticationExtensions
{
public static AuthenticationBuilder AddBasicAuth(this AuthenticationBuilder builder)
{
return builder.AddScheme<BasicAuthenticationOptions, BasicAuthenticationHandler>(
nameof(AuthenticationSchemes.Basic),
o => { });
}
}
}

View File

@ -1,76 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.Extensions;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace BtcTransmuter.Auth
{
// ReSharper disable once ClassNeverInstantiated.Global
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
{
private readonly IOptionsMonitor<IdentityOptions> _identityOptions;
private readonly SignInManager<User> _signInManager;
private readonly UserManager<User> _userManager;
public BasicAuthenticationHandler(
IOptionsMonitor<IdentityOptions> identityOptions,
IOptionsMonitor<BasicAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
SignInManager<User> signInManager,
UserManager<User> userManager) : base(options, logger, encoder, clock)
{
_identityOptions = identityOptions;
_signInManager = signInManager;
_userManager = userManager;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string authHeader = Context.Request.Headers["Authorization"];
if (authHeader == null || !authHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
return AuthenticateResult.NoResult();
var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();
var decodedUsernamePassword =
Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword)).Split(':');
var username = decodedUsernamePassword[0];
var password = decodedUsernamePassword[1];
var result = await _signInManager.PasswordSignInAsync(username, password, true, true);
if (!result.Succeeded)
return AuthenticateResult.Fail(result.ToString());
var user = await _userManager.FindByNameAsync(username);
if (!user.Get<UserBlob>().BasicAuth)
{
return AuthenticateResult.Fail("This user does not have basic auth enabled.");
}
var claims = new List<Claim>()
{
new Claim(_identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, user.Id)
};
var roles = await _userManager.GetRolesAsync(user);
claims.AddRange(roles.Select(s => new Claim(_identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, s) ));
Context.SetIsApi(true);
return AuthenticateResult.Success(new AuthenticationTicket(
new ClaimsPrincipal(new ClaimsIdentity(claims, nameof(AuthenticationSchemes.Basic))), nameof(AuthenticationSchemes.Basic)));
}
}
}

View File

@ -1,8 +0,0 @@
using Microsoft.AspNetCore.Authentication;
namespace BtcTransmuter.Auth
{
public class BasicAuthenticationOptions : AuthenticationSchemeOptions
{
}
}

View File

@ -1,16 +0,0 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Server.HttpSys;
namespace BtcTransmuter.Auth
{
public class TransmuterSchemes
{
public const string AllSchemes = Local + "," + API;
public const string API = Basic;
public const string Basic = nameof(AuthenticationSchemes.Basic);
//IdentityConstants.ApplicationScheme
public const string Local = "Identity.Application" + "," + CookieAuthenticationDefaults.AuthenticationScheme;
}
}

View File

@ -1,230 +0,0 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using BtcTransmuter.Areas.Identity.Pages.Account;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BtcTransmuter
{
public class BTCPayAuthService
{
private readonly UserManager<User> _userManager;
private readonly IBtcTransmuterOptions _btcTransmuterOptions;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<BTCPayAuthService> _logger;
public BTCPayAuthService(
UserManager<User> userManager, IBtcTransmuterOptions btcTransmuterOptions,
IHttpClientFactory httpClientFactory, ILogger<BTCPayAuthService> logger)
{
_userManager = userManager;
_btcTransmuterOptions = btcTransmuterOptions;
_httpClientFactory = httpClientFactory;
_logger = logger;
}
public async Task<User> LoginAndRegisterIfNeeded(string user, string pass)
{
if (_btcTransmuterOptions.BTCPayAuthServer is null)
{
return null;
}
var response = await BasicAuthLogin(user, pass);
if (response == null)
{
return null;
}
var matchedUser = await FindUserByBTCPayUserId(response.Id);
if (matchedUser == null)
{
var key = await GenerateKey(user, pass);
if (string.IsNullOrEmpty(key))
{
return null;
}
//create account
matchedUser = new User()
{
Email = response.Email,
Id = response.Id,
UserName = response.Email,
};
matchedUser.Set(new UserBlob()
{
BTCPayAuthDetails = new BTCPayAuthDetails()
{
UserId = response.Id,
AccessToken = key
}
});
if ((await _userManager.CreateAsync(matchedUser)).Succeeded)
{
if (response.Roles.Contains("ServerAdmin") || await _userManager.Users.CountAsync() == 1)
{
await _userManager.AddToRoleAsync(matchedUser, "Admin");
}
}
else
{
return null;
}
}
else
{
var tokenResponse = await CheckToken(matchedUser);
if (!(tokenResponse?.ToString()?.Equals(response.ToString()) is true) &&
await GenerateKeyAndSet(user, pass, matchedUser))
{
await _userManager.UpdateAsync(matchedUser);
}
else if (!(tokenResponse?.ToString()?.Equals(response.ToString()) is true))
{
return null;
}
}
if (response.Roles.Contains("ServerAdmin"))
{
await _userManager.AddToRoleAsync(matchedUser, "Admin");
}
else if(!await _userManager.HasPasswordAsync(matchedUser))
{
await _userManager.RemoveFromRoleAsync(matchedUser, "Admin");
}
return matchedUser;
}
private async Task<bool> GenerateKeyAndSet(string user, string pass, User matchedUser)
{
var key = await GenerateKey(user, pass);
if (string.IsNullOrEmpty(key))
{
return false;
}
var blob = matchedUser.Get<UserBlob>();
blob.BTCPayAuthDetails.AccessToken = key;
if (_btcTransmuterOptions.DisableInternalAuth || !await _userManager.HasPasswordAsync(matchedUser))
{
matchedUser.Email = user;
matchedUser.UserName = user;
}
matchedUser.Set(blob);
return true;
}
public async Task<string> GenerateKey(string user, string pass)
{
var client = _httpClientFactory.CreateClient("BTCPayAuthServer");
var request = new HttpRequestMessage(HttpMethod.Post,
new Uri(_btcTransmuterOptions.BTCPayAuthServer, "/api/v1/api-keys"));
request.Headers.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(Encoding.UTF8.GetBytes($"{user}:{pass}")));
request.Content = new StringContent(JsonConvert.SerializeObject(new
{
label = "transmuter login access token",
permissions = new[] {"btcpay.user.canmodifyprofile"}
}), Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var accessTokenResponse =
JsonConvert.DeserializeObject<JObject>((await response.Content.ReadAsStringAsync()));
return accessTokenResponse["apiKey"].Value<string>();
}
return null;
}
public async Task<User> FindUserByBTCPayUserId(string userId)
{
return _userManager.Users.AsEnumerable().SingleOrDefault(user =>
user.Get<UserBlob>().BTCPayAuthDetails.UserId == userId);
}
public async Task<GetCurrentUserResponse> BasicAuthLogin(string user, string pass)
{
if (_btcTransmuterOptions.BTCPayAuthServer is null)
{
return null;
}
try
{
var client = _httpClientFactory.CreateClient("BTCPayAuthServer");
var fetchUserId = new Uri(_btcTransmuterOptions.BTCPayAuthServer, "api/v1/users/me");
var request = new HttpRequestMessage(HttpMethod.Get, fetchUserId);
request.Headers.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(Encoding.UTF8.GetBytes($"{user}:{pass}")));
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return JsonConvert.DeserializeObject<GetCurrentUserResponse>(
await response.Content.ReadAsStringAsync());
}
}
catch (Exception e)
{
_logger.LogError(e, "error while attempting to authenticate with btcpay");
}
return null;
}
public async Task<GetCurrentUserResponse> CheckToken(User user)
{
if (user == null)
{
return null;
}
var blob = user.Get<UserBlob>();
return await CheckToken(blob.BTCPayAuthDetails.AccessToken);
}
public async Task<GetCurrentUserResponse> CheckToken(string token)
{
if (_btcTransmuterOptions.BTCPayAuthServer is null)
{
return null;
}
var client = _httpClientFactory.CreateClient("BTCPayAuthServer");
if (string.IsNullOrEmpty(token))
{
return null;
}
var fetchUserId = new Uri(_btcTransmuterOptions.BTCPayAuthServer, "api/v1/users/me");
var request = new HttpRequestMessage(HttpMethod.Get, fetchUserId);
request.Headers.Authorization = new AuthenticationHeaderValue("token", token);
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
return null;
}
return JsonConvert.DeserializeObject<GetCurrentUserResponse>(
await response.Content.ReadAsStringAsync());
}
}
}

View File

@ -6,9 +6,9 @@
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<UserSecretsId>aspnet-BtcTransmuter-065A2226-96CE-4371-A96D-97EFFCF0B153</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<Version>0.0.59</Version>
<Version>0.0.49</Version>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageVersion>0.0.59</PackageVersion>
<PackageVersion>0.0.49</PackageVersion>
</PropertyGroup>
@ -17,13 +17,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.0" />
</ItemGroup>
<ItemGroup>

View File

@ -1,9 +1,9 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace BtcTransmuter
@ -15,19 +15,11 @@ namespace BtcTransmuter
{
RootPath = configuration.GetValue("RootPath", "");
BTCPayAuthServer = configuration.GetValue<Uri>("BTCPayAuthServer", null);
DatabaseConnectionString = configuration.GetValue<string>("Database");
DataProtectionDir = configuration.GetValue<string>("DataProtectionDir");
DataProtectionApplicationName = configuration.GetValue<string>("DataProtectionApplicationName");
DatabaseType = configuration.GetValue<DatabaseType>("DatabaseType", DatabaseType.Sqlite);
UseDatabaseColumnEncryption = configuration.GetValue<bool>("UseDatabaseColumnEncryption", false);
DisableInternalAuth = configuration.GetValue<bool>("DisableInternalAuth", false);
if (DisableInternalAuth && BTCPayAuthServer == null)
{
DisableInternalAuth = false;
logger.LogWarning($"Cannot disable internal auth while not setting BTCPayAuthServer");
}
ExtensionsDir = configuration.GetValue<string>("ExtensionsDir",
Path.Combine(hostingEnvironment.ContentRootPath, "Extensions"));
@ -57,9 +49,5 @@ namespace BtcTransmuter
public DatabaseType DatabaseType { get; set; }
public bool UseDatabaseColumnEncryption { get; set; }
public bool DisableInternalAuth { get; set; }
public Uri BTCPayAuthServer { get; set; }
}
}

View File

@ -1,7 +1,6 @@
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.Recipes;
using BtcTransmuter.Abstractions.Settings;
using BtcTransmuter.Auth;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Models;
using Microsoft.AspNetCore.Authorization;
@ -11,7 +10,7 @@ using Microsoft.EntityFrameworkCore;
namespace BtcTransmuter.Controllers
{
[Authorize(Roles = "Admin", AuthenticationSchemes = TransmuterSchemes.Local)]
[Authorize(Roles = "Admin")]
[Route("[controller]")]
public class AdminController : Controller
{

Some files were not shown because too many files have changed in this diff Show More