diff --git a/BTCPayServer.Client/BTCPayServerClient.StoreEmail.cs b/BTCPayServer.Client/BTCPayServerClient.StoreEmail.cs index 128cc63a0..7932c4864 100644 --- a/BTCPayServer.Client/BTCPayServerClient.StoreEmail.cs +++ b/BTCPayServer.Client/BTCPayServerClient.StoreEmail.cs @@ -7,6 +7,24 @@ namespace BTCPayServer.Client { public partial class BTCPayServerClient { + public virtual async Task GetStoreEmailSettings(string storeId, + CancellationToken token = default) + { + using var response = await _httpClient.SendAsync( + CreateHttpRequest($"api/v1/stores/{storeId}/email", method: HttpMethod.Get), + token); + return await HandleResponse(response); + } + + public virtual async Task UpdateStoreEmailSettings(string storeId, EmailSettingsData request, + CancellationToken token = default) + { + using var response = await _httpClient.SendAsync( + CreateHttpRequest($"api/v1/stores/{storeId}/email", bodyPayload: request, method: HttpMethod.Put), + token); + return await HandleResponse(response); + } + public virtual async Task SendEmail(string storeId, SendEmailRequest request, CancellationToken token = default) { diff --git a/BTCPayServer.Client/BTCPayServerClient.StoreUsers.cs b/BTCPayServer.Client/BTCPayServerClient.StoreUsers.cs index d8387e5a8..20ddc4793 100644 --- a/BTCPayServer.Client/BTCPayServerClient.StoreUsers.cs +++ b/BTCPayServer.Client/BTCPayServerClient.StoreUsers.cs @@ -23,7 +23,7 @@ namespace BTCPayServer.Client await HandleResponse(response); } - public virtual async Task AddStoreUser(string storeId, StoreUserData request, + public virtual async Task AddStoreUser(string storeId, StoreUserData request, CancellationToken token = default) { if (request == null) @@ -31,7 +31,7 @@ namespace BTCPayServer.Client using var response = await _httpClient.SendAsync( CreateHttpRequest($"api/v1/stores/{storeId}/users", bodyPayload: request, method: HttpMethod.Post), token); - return await HandleResponse(response); + await HandleResponse(response); } } } diff --git a/BTCPayServer.Client/Models/EmailSettingsData.cs b/BTCPayServer.Client/Models/EmailSettingsData.cs new file mode 100644 index 000000000..a587dfd43 --- /dev/null +++ b/BTCPayServer.Client/Models/EmailSettingsData.cs @@ -0,0 +1,33 @@ +namespace BTCPayServer.Client.Models; + +public class EmailSettingsData +{ + public string Server + { + get; set; + } + + public int? Port + { + get; set; + } + + public string Login + { + get; set; + } + + public string Password + { + get; set; + } + + public string FromDisplay + { + get; set; + } + public string From + { + get; set; + } +} diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index 13f90754e..02e151283 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -2292,13 +2292,27 @@ namespace BTCPayServer.Tests var admin = tester.NewAccount(); await admin.GrantAccessAsync(true); var adminClient = await admin.CreateClient(Policies.Unrestricted); + await adminClient.UpdateStoreEmailSettings(admin.StoreId, + new EmailSettingsData()); - await adminClient.SendEmail(admin.StoreId, new SendEmailRequest() + var data = new EmailSettingsData() { - Body = "lol", - Subject = "subj", - Email = "sdasdas" - }); + From = "admin@admin.com", + Login = "admin@admin.com", + Password = "admin@admin.com", + Port = 1234, + Server = "admin.com", + }; + await adminClient.UpdateStoreEmailSettings(admin.StoreId, data); + var s = await adminClient.GetStoreEmailSettings(admin.StoreId); + Assert.Equal(JsonConvert.SerializeObject(s), JsonConvert.SerializeObject(data)); + await AssertValidationError(new[] { nameof(EmailSettingsData.From) }, + async () => await adminClient.UpdateStoreEmailSettings(admin.StoreId, + new EmailSettingsData() { From = "ass" })); + + + await adminClient.SendEmail(admin.StoreId, + new SendEmailRequest() { Body = "lol", Subject = "subj", Email = "sdasdas" }); } } } diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs index 84d4de7fc..d18eaba15 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs @@ -5,7 +5,10 @@ using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; using BTCPayServer.Client.Models; +using BTCPayServer.Data; using BTCPayServer.Services.Mails; +using BTCPayServer.Services.Stores; +using BTCPayServer.Validation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; @@ -18,10 +21,12 @@ namespace BTCPayServer.Controllers.GreenField public class GreenfieldStoreEmailController : Controller { private readonly EmailSenderFactory _emailSenderFactory; + private readonly StoreRepository _storeRepository; - public GreenfieldStoreEmailController(EmailSenderFactory emailSenderFactory) + public GreenfieldStoreEmailController(EmailSenderFactory emailSenderFactory, StoreRepository storeRepository) { _emailSenderFactory = emailSenderFactory; + _storeRepository = storeRepository; } [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] @@ -43,5 +48,48 @@ namespace BTCPayServer.Controllers.GreenField emailSender.SendEmail(request.Email, request.Subject, request.Body); return Ok(); } + + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] + [HttpGet("~/api/v1/stores/{storeId}/email")] + public IActionResult GetStoreEmailSettings() + { + + var store = HttpContext.GetStoreData(); + return store == null ? StoreNotFound() : Ok(FromModel(store)); + } + + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] + [HttpPut("~/api/v1/stores/{storeId}/email")] + public async Task UpdateStoreEmailSettings(string storeId, EmailSettings request) + { + var store = HttpContext.GetStoreData(); + if (store == null) + { + return StoreNotFound(); + } + + if (!string.IsNullOrEmpty(request.From) && !EmailValidator.IsEmail(request.From)) + { + request.AddModelError(e => e.From, + "Invalid email address", this); + return this.CreateValidationError(ModelState); + } + var blob = store.GetStoreBlob(); + blob.EmailSettings = request; + if (store.SetStoreBlob(blob)) + { + await _storeRepository.UpdateStore(store); + } + + return Ok(FromModel(store)); + } + private EmailSettings FromModel(Data.StoreData data) + { + return data.GetStoreBlob().EmailSettings??new(); + } + private IActionResult StoreNotFound() + { + return this.CreateAPIError(404, "store-not-found", "The store was not found"); + } } } diff --git a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs index a657fa2df..0ce789306 100644 --- a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs +++ b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs @@ -12,6 +12,7 @@ using BTCPayServer.Client.Models; using BTCPayServer.Controllers.GreenField; using BTCPayServer.Data; using BTCPayServer.Security.Greenfield; +using BTCPayServer.Services.Mails; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -22,6 +23,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NBitcoin; using NBXplorer.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using InvoiceData = BTCPayServer.Client.Models.InvoiceData; using Language = BTCPayServer.Client.Models.Language; using NotificationData = BTCPayServer.Client.Models.NotificationData; @@ -40,7 +43,10 @@ namespace BTCPayServer.Controllers.Greenfield private readonly IOptionsMonitor _identityOptions; private readonly GreenfieldStoreOnChainPaymentMethodsController _chainPaymentMethodsController; private readonly GreenfieldStoreOnChainWalletsController _storeOnChainWalletsController; - private readonly GreenfieldStoreLightningNetworkPaymentMethodsController _storeLightningNetworkPaymentMethodsController; + + private readonly GreenfieldStoreLightningNetworkPaymentMethodsController + _storeLightningNetworkPaymentMethodsController; + private readonly GreenfieldStoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController; private readonly GreenfieldHealthController _healthController; private readonly GreenfieldPaymentRequestsController _paymentRequestController; @@ -58,6 +64,7 @@ namespace BTCPayServer.Controllers.Greenfield private readonly UIHomeController _homeController; private readonly GreenfieldStorePaymentMethodsController _storePaymentMethodsController; private readonly GreenfieldStoreEmailController _greenfieldStoreEmailController; + private readonly GreenfieldStoreUsersController _greenfieldStoreUsersController; private readonly IServiceProvider _serviceProvider; public BTCPayServerClientFactory(StoreRepository storeRepository, @@ -82,6 +89,7 @@ namespace BTCPayServer.Controllers.Greenfield UIHomeController homeController, GreenfieldStorePaymentMethodsController storePaymentMethodsController, GreenfieldStoreEmailController greenfieldStoreEmailController, + GreenfieldStoreUsersController greenfieldStoreUsersController, IServiceProvider serviceProvider) { _storeRepository = storeRepository; @@ -106,6 +114,7 @@ namespace BTCPayServer.Controllers.Greenfield _homeController = homeController; _storePaymentMethodsController = storePaymentMethodsController; _greenfieldStoreEmailController = greenfieldStoreEmailController; + _greenfieldStoreUsersController = greenfieldStoreUsersController; _serviceProvider = serviceProvider; } @@ -124,12 +133,14 @@ namespace BTCPayServer.Controllers.Greenfield claims.AddRange((await _userManager.GetRolesAsync(user)).Select(s => new Claim(_identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, s))); context.User = - new ClaimsPrincipal(new ClaimsIdentity(claims, $"Local{GreenfieldConstants.AuthenticationType}WithUser")); + new ClaimsPrincipal(new ClaimsIdentity(claims, + $"Local{GreenfieldConstants.AuthenticationType}WithUser")); } else { context.User = - new ClaimsPrincipal(new ClaimsIdentity(new List(), $"Local{GreenfieldConstants.AuthenticationType}")); + new ClaimsPrincipal(new ClaimsIdentity(new List(), + $"Local{GreenfieldConstants.AuthenticationType}")); } if (storeIds?.Any() is true) @@ -163,15 +174,16 @@ namespace BTCPayServer.Controllers.Greenfield _homeController, _storePaymentMethodsController, _greenfieldStoreEmailController, + _greenfieldStoreUsersController, new LocalHttpContextAccessor() { HttpContext = context } ); } } - public class LocalHttpContextAccessor: IHttpContextAccessor + public class LocalHttpContextAccessor : IHttpContextAccessor { public HttpContext? HttpContext { get; set; } - } + } public class LocalBTCPayServerClient : BTCPayServerClient { @@ -185,7 +197,10 @@ namespace BTCPayServer.Controllers.Greenfield private readonly GreenfieldStoresController _storesController; private readonly GreenfieldStoreLightningNodeApiController _storeLightningNodeApiController; private readonly GreenfieldInternalLightningNodeApiController _lightningNodeApiController; - private readonly GreenfieldStoreLightningNetworkPaymentMethodsController _storeLightningNetworkPaymentMethodsController; + + private readonly GreenfieldStoreLightningNetworkPaymentMethodsController + _storeLightningNetworkPaymentMethodsController; + private readonly GreenfieldStoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController; private readonly GreenfieldInvoiceController _greenFieldInvoiceController; private readonly GreenfieldServerInfoController _greenFieldServerInfoController; @@ -194,6 +209,7 @@ namespace BTCPayServer.Controllers.Greenfield private readonly UIHomeController _homeController; private readonly GreenfieldStorePaymentMethodsController _storePaymentMethodsController; private readonly GreenfieldStoreEmailController _greenfieldStoreEmailController; + private readonly GreenfieldStoreUsersController _greenfieldStoreUsersController; public LocalBTCPayServerClient( IServiceProvider serviceProvider, @@ -216,6 +232,7 @@ namespace BTCPayServer.Controllers.Greenfield UIHomeController homeController, GreenfieldStorePaymentMethodsController storePaymentMethodsController, GreenfieldStoreEmailController greenfieldStoreEmailController, + GreenfieldStoreUsersController greenfieldStoreUsersController, IHttpContextAccessor httpContextAccessor) : base(new Uri("https://dummy.local"), "", "") { _chainPaymentMethodsController = chainPaymentMethodsController; @@ -237,6 +254,7 @@ namespace BTCPayServer.Controllers.Greenfield _homeController = homeController; _storePaymentMethodsController = storePaymentMethodsController; _greenfieldStoreEmailController = greenfieldStoreEmailController; + _greenfieldStoreUsersController = greenfieldStoreUsersController; var controllers = new[] { @@ -244,14 +262,14 @@ namespace BTCPayServer.Controllers.Greenfield paymentRequestController, apiKeysController, notificationsController, usersController, storeLightningNetworkPaymentMethodsController, greenFieldInvoiceController, storeWebhooksController, greenFieldServerInfoController, greenfieldPullPaymentController, storesController, homeController, - lightningNodeApiController, storeLightningNodeApiController as ControllerBase, storePaymentMethodsController, - greenfieldStoreEmailController + lightningNodeApiController, storeLightningNodeApiController as ControllerBase, + storePaymentMethodsController, greenfieldStoreEmailController, greenfieldStoreUsersController }; var authoverride = new DefaultAuthorizationService( serviceProvider.GetRequiredService(), new AuthHandlerProvider( - serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService>(), httpContextAccessor ), @@ -259,16 +277,15 @@ namespace BTCPayServer.Controllers.Greenfield serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService>() - - ); - - + + foreach (var controller in controllers) { controller.ControllerContext.HttpContext = httpContextAccessor.HttpContext; var authInterface = typeof(IAuthorizationService); - foreach (FieldInfo fieldInfo in controller.GetType().GetFields().Where(info => authInterface.IsAssignableFrom(info.FieldType))) + foreach (FieldInfo fieldInfo in controller.GetType().GetFields() + .Where(info => authInterface.IsAssignableFrom(info.FieldType))) { fieldInfo.SetValue(controller, authoverride); } @@ -283,12 +300,14 @@ namespace BTCPayServer.Controllers.Greenfield private readonly UserManager _userManager; private readonly StoreRepository _storeRepository; - public AuthHandlerProvider(StoreRepository storeRepository, UserManager userManager, IHttpContextAccessor httpContextAccessor) + public AuthHandlerProvider(StoreRepository storeRepository, UserManager userManager, + IHttpContextAccessor httpContextAccessor) { _storeRepository = storeRepository; _userManager = userManager; _httpContextAccessor = httpContextAccessor; } + public Task> GetHandlersAsync(AuthorizationHandlerContext context) { return Task.FromResult>(new IAuthorizationHandler[] @@ -297,6 +316,7 @@ namespace BTCPayServer.Controllers.Greenfield }); } } + protected override HttpRequestMessage CreateHttpRequest(string path, Dictionary queryPayload = null, HttpMethod method = null) { @@ -511,7 +531,8 @@ namespace BTCPayServer.Controllers.Greenfield await _lightningNodeApiController.GetDepositAddress(cryptoCode)); } - public override async Task PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request, + public override async Task PayLightningInvoice(string cryptoCode, + PayLightningInvoiceRequest request, CancellationToken token = default) { return GetFromActionResult( @@ -575,7 +596,8 @@ namespace BTCPayServer.Controllers.Greenfield public override Task> GetStoreOnChainPaymentMethods(string storeId, bool? enabled, CancellationToken token) { - return Task.FromResult(GetFromActionResult(_chainPaymentMethodsController.GetOnChainPaymentMethods(storeId, enabled))); + return Task.FromResult( + GetFromActionResult(_chainPaymentMethodsController.GetOnChainPaymentMethods(storeId, enabled))); } public override Task GetStoreOnChainPaymentMethod(string storeId, @@ -596,17 +618,19 @@ namespace BTCPayServer.Controllers.Greenfield CancellationToken token = default) { return GetFromActionResult( - await _chainPaymentMethodsController.UpdateOnChainPaymentMethod(storeId, cryptoCode, new UpdateOnChainPaymentMethodRequest( - enabled: paymentMethod.Enabled, - label: paymentMethod.Label, - accountKeyPath: paymentMethod.AccountKeyPath, - derivationScheme: paymentMethod.DerivationScheme - ))); + await _chainPaymentMethodsController.UpdateOnChainPaymentMethod(storeId, cryptoCode, + new UpdateOnChainPaymentMethodRequest( + enabled: paymentMethod.Enabled, + label: paymentMethod.Label, + accountKeyPath: paymentMethod.AccountKeyPath, + derivationScheme: paymentMethod.DerivationScheme + ))); } public override Task PreviewProposedStoreOnChainPaymentMethodAddresses( string storeId, string cryptoCode, - UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0, int amount = 10, CancellationToken token = default) + UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0, int amount = 10, + CancellationToken token = default) { return Task.FromResult(GetFromActionResult( _chainPaymentMethodsController.GetProposedOnChainPaymentMethodPreview(storeId, cryptoCode, @@ -618,7 +642,7 @@ namespace BTCPayServer.Controllers.Greenfield CancellationToken token = default) { return Task.FromResult(GetFromActionResult( - _chainPaymentMethodsController.GetOnChainPaymentMethodPreview(storeId, cryptoCode, offset, + _chainPaymentMethodsController.GetOnChainPaymentMethodPreview(storeId, cryptoCode, offset, amount))); } @@ -636,7 +660,8 @@ namespace BTCPayServer.Controllers.Greenfield public override async Task GetPaymentRequest(string storeId, string paymentRequestId, CancellationToken token = default) { - return GetFromActionResult(await _paymentRequestController.GetPaymentRequest(storeId, paymentRequestId)); + return GetFromActionResult( + await _paymentRequestController.GetPaymentRequest(storeId, paymentRequestId)); } public override async Task ArchivePaymentRequest(string storeId, string paymentRequestId, @@ -882,7 +907,8 @@ namespace BTCPayServer.Controllers.Greenfield { return GetFromActionResult(await _storeLightningNetworkPaymentMethodsController.UpdateLightningNetworkPaymentMethod(storeId, cryptoCode, - new UpdateLightningNetworkPaymentMethodRequest(paymentMethod.ConnectionString, paymentMethod.Enabled))); + new UpdateLightningNetworkPaymentMethodRequest(paymentMethod.ConnectionString, + paymentMethod.Enabled))); } public override async Task> GetInvoices(string storeId, string[] orderId = null, @@ -894,8 +920,7 @@ namespace BTCPayServer.Controllers.Greenfield int? skip = null, int? take = null, CancellationToken token = default - - ) + ) { return GetFromActionResult>( await _greenFieldInvoiceController.GetInvoices(storeId, orderId, @@ -980,7 +1005,8 @@ namespace BTCPayServer.Controllers.Greenfield public override Task GetAvailableLanguages(CancellationToken token = default) { - return Task.FromResult(_homeController.LanguageService.GetLanguages().Select(language => new Language(language.Code, language.DisplayName)).ToArray()); + return Task.FromResult(_homeController.LanguageService.GetLanguages() + .Select(language => new Language(language.Code, language.DisplayName)).ToArray()); } public override Task GetPermissionMetadata(CancellationToken token = default) @@ -988,30 +1014,78 @@ namespace BTCPayServer.Controllers.Greenfield return Task.FromResult(GetFromActionResult(_homeController.Permissions())); } - public override async Task> GetStorePaymentMethods(string storeId, bool? enabled = null, CancellationToken token = default) + public override async Task> GetStorePaymentMethods(string storeId, + bool? enabled = null, CancellationToken token = default) { return GetFromActionResult(await _storePaymentMethodsController.GetStorePaymentMethods(storeId, enabled)); } - public override async Task GenerateOnChainWallet(string storeId, string cryptoCode, GenerateOnChainWalletRequest request, + public override async Task GenerateOnChainWallet(string storeId, + string cryptoCode, GenerateOnChainWalletRequest request, CancellationToken token = default) { - return GetFromActionResult(await _chainPaymentMethodsController.GenerateOnChainWallet(storeId, cryptoCode, new GenerateWalletRequest() - { - Passphrase = request.Passphrase, - AccountNumber = request.AccountNumber, - ExistingMnemonic = request.ExistingMnemonic?.ToString(), - WordCount = request.WordCount, - WordList = request.WordList, - SavePrivateKeys = request.SavePrivateKeys, - ScriptPubKeyType = request.ScriptPubKeyType, - ImportKeysToRPC = request.ImportKeysToRPC - })); + return GetFromActionResult( + await _chainPaymentMethodsController.GenerateOnChainWallet(storeId, cryptoCode, + new GenerateWalletRequest() + { + Passphrase = request.Passphrase, + AccountNumber = request.AccountNumber, + ExistingMnemonic = request.ExistingMnemonic?.ToString(), + WordCount = request.WordCount, + WordList = request.WordList, + SavePrivateKeys = request.SavePrivateKeys, + ScriptPubKeyType = request.ScriptPubKeyType, + ImportKeysToRPC = request.ImportKeysToRPC + })); } - public override async Task SendEmail(string storeId, SendEmailRequest request, CancellationToken token = default) + public override async Task SendEmail(string storeId, SendEmailRequest request, + CancellationToken token = default) { HandleActionResult(await _greenfieldStoreEmailController.SendEmailFromStore(storeId, request)); } + + public override Task GetStoreEmailSettings(string storeId, CancellationToken token = default) + { + return Task.FromResult( + GetFromActionResult(_greenfieldStoreEmailController.GetStoreEmailSettings())); + } + + public override async Task UpdateStoreEmailSettings(string storeId, + EmailSettingsData request, CancellationToken token = default) + { + return GetFromActionResult( + await _greenfieldStoreEmailController.UpdateStoreEmailSettings(storeId, + JObject.FromObject(request).ToObject())); + } + + public override async Task GetUsers(CancellationToken token = default) + { + return GetFromActionResult(await _usersController.GetUsers()); + } + + public override Task> GetStoreUsers(string storeId, + CancellationToken token = default) + { + return Task.FromResult( + GetFromActionResult>(_greenfieldStoreUsersController.GetStoreUsers())); + } + + public override async Task AddStoreUser(string storeId, StoreUserData request, + CancellationToken token = default) + { + HandleActionResult(await _greenfieldStoreUsersController.AddStoreUser(storeId, request)); + } + + public override async Task RemoveStoreUser(string storeId, string userId, CancellationToken token = default) + { + HandleActionResult(await _greenfieldStoreUsersController.RemoveStoreUser(storeId, userId)); + } + + public override async Task GetUserByIdOrEmail(string idOrEmail, + CancellationToken token = default) + { + return GetFromActionResult(await _usersController.GetUser(idOrEmail)); + } } } diff --git a/BTCPayServer/Services/Mails/EmailSettings.cs b/BTCPayServer/Services/Mails/EmailSettings.cs index b7e211d6d..ce0b06b87 100644 --- a/BTCPayServer/Services/Mails/EmailSettings.cs +++ b/BTCPayServer/Services/Mails/EmailSettings.cs @@ -4,48 +4,15 @@ using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Client.Models; using MailKit.Net.Smtp; using MimeKit; using Newtonsoft.Json; namespace BTCPayServer.Services.Mails { - public class EmailSettings + public class EmailSettings :EmailSettingsData { - [Display(Name = "SMTP Server")] - public string Server - { - get; set; - } - - public int? Port - { - get; set; - } - - public string Login - { - get; set; - } - - public string Password - { - get; set; - } - - [Display(Name = "Sender's display name")] - public string FromDisplay - { - get; set; - } - - [EmailAddress] - [Display(Name = "Sender's email address")] - public string From - { - get; set; - } - public bool IsComplete() { return !string.IsNullOrWhiteSpace(Server) && diff --git a/BTCPayServer/Views/Shared/EmailsBody.cshtml b/BTCPayServer/Views/Shared/EmailsBody.cshtml index 937f9f35a..281cf4ebd 100644 --- a/BTCPayServer/Views/Shared/EmailsBody.cshtml +++ b/BTCPayServer/Views/Shared/EmailsBody.cshtml @@ -28,7 +28,7 @@
}
- +
@@ -38,7 +38,7 @@
- + Some email providers (like Gmail) don't allow you to set your display name, so this setting may not have any effect. @@ -46,7 +46,7 @@
- +
diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-email.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-email.json index ec530bf02..2370a9ee2 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-email.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-email.json @@ -1,9 +1,109 @@ { "paths": { + "/api/v1/stores/{storeId}/email": { + "get": { + "tags": [ + "Stores (Email)" + ], + "summary": "Get store email settings", + "parameters": [ + { + "name": "storeId", + "in": "path", + "required": true, + "description": "The store to fetch", + "schema": { + "type": "string" + } + } + ], + "description": "View email settings of the specified store", + "operationId": "Stores_GetStoreEmailSettings", + "responses": { + "200": { + "description": "specified store email settings", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailSettingsData" + } + } + } + }, + "403": { + "description": "If you are authenticated but forbidden to view the specified store" + }, + "404": { + "description": "The key is not found for this store" + } + }, + "security": [ + { + "API_Key": [ + "btcpay.store.canmodifystoresettings" + ], + "Basic": [] + } + ] + }, + "post": { + "tags": [ + "Stores (Email)" + ], + "summary": "Update store email settings", + "description": "Update a store's email settings", + "operationId": "Stores_UpdateStoreEmailSettings", + "requestBody": { + "x-name": "request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailSettingsData" + } + } + }, + "required": true, + "x-position": 1 + }, + "responses": { + "200": { + "description": "The settings were updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailSettingsData" + } + } + } + }, + "400": { + "description": "A list of errors that occurred when updating the settings", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationProblemDetails" + } + } + } + }, + "403": { + "description": "If you are authenticated but forbidden to modify the store" + } + }, + "security": [ + { + "API_Key": [ + "btcpay.store.canmodifystoresettings" + ], + "Basic": [] + } + ] + } + }, "/api/v1/stores/{storeId}/email/send": { "post": { "tags": [ - "Stores" + "Stores (Email)" ], "summary": "Send an email for a store", "description": "Send an email using the store's SMTP server", @@ -63,12 +163,43 @@ "description": "Body of the email to send as plain text." } } + }, + "EmailSettingsData": { + "type": "object", + "additionalProperties": false, + "properties": { + "server": { + "type": "string", + "description": "Smtp server host" + }, + "port": { + "type": "number", + "description": "Smtp server port" + }, + "login": { + "type": "string", + "description": "Smtp server username" + }, + "password": { + "type": "string", + "description": "Smtp server password" + }, + "from": { + "type": "string", + "format": "email", + "description": "Email to send from" + }, + "fromDisplay": { + "type": "string", + "description": "The name of the sender" + } + } } } }, "tags": [ { - "name": "Store Email" + "name": "Stores (Email)" } ] } diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-users.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-users.json index 7e9eb74e1..a1f7b043b 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-users.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-users.json @@ -52,6 +52,7 @@ ], "summary": "Add a store user", "description": "Add a store user", + "operationId": "Stores_AddStoreUser", "requestBody": { "x-name": "request", "content": { @@ -66,7 +67,7 @@ }, "responses": { "200": { - "description": "The user as added" + "description": "The user was added" }, "400": { "description": "A list of errors that occurred when creating the store", @@ -108,6 +109,7 @@ "Stores (Users)" ], "summary": "Remove Store User", + "operationId": "Stores_RemoveStoreUser", "description": "Removes the specified store user. If there is no other owner, this endpoint will fail.", "parameters": [ {