#nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Client; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; namespace BTCPayServer.Plugins.NIP05; [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Route("stores/{storeId}/plugins/nip5")] public class Nip5Controller : Controller { private readonly StoreRepository _storeRepository; private readonly IMemoryCache _memoryCache; public Nip5Controller() { } public Nip5Controller(StoreRepository storeRepository, IMemoryCache memoryCache) { _storeRepository = storeRepository; _memoryCache = memoryCache; } [HttpGet] public async Task Edit(string storeId) { var settings = await _storeRepository.GetSettingAsync(storeId, "NIP05"); return View(settings ?? new()); } [HttpPost] public async Task Edit(string storeId, Nip5StoreSettings settings, string command) { if (command == "remove") { var settingss = await _storeRepository.GetSettingAsync(storeId, "NIP05"); if (settingss is not null) { await _storeRepository.UpdateSetting(storeId, "NIP05", null); _memoryCache.Remove($"NIP05_{settingss.Name.ToLowerInvariant()}"); } return RedirectToAction("Edit", new {storeId}); } if (!ModelState.IsValid) { return View(settings); } settings.Relays = settings.Relays ?.Where(s => !string.IsNullOrEmpty(s) && Uri.TryCreate(s, UriKind.Absolute, out _)).ToArray(); var found = await Get(settings.Name.ToLowerInvariant()); if (found.storeId is not null && storeId != found.storeId) { ModelState.AddModelError(nameof(settings.Name), "Name is already in use. Choose something else"); return View(settings); } var settingssx = await _storeRepository.GetSettingAsync(storeId, "NIP05"); if (settingssx is not null) { _memoryCache.Remove($"NIP05_{settingssx.Name.ToLowerInvariant()}"); } await _storeRepository.UpdateSetting(storeId, "NIP05", settings); return RedirectToAction("Edit", new {storeId}); } private async Task<(string? storeId, Nip5StoreSettings? settings)> Get(string name) { var rex = await _memoryCache.GetOrCreateAsync<(string? storeId, Nip5StoreSettings? settings)>( $"NIP05_{name.ToLowerInvariant()}", async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10); var store = await _storeRepository.GetSettingsAsync("NIP05"); KeyValuePair matched = store.FirstOrDefault(pair => pair.Value.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); return (matched.Key, matched.Value); }); if (rex.storeId is null) { _memoryCache.Remove($"NIP05_{name.ToLowerInvariant()}"); } return rex; } [HttpGet("~/.well-known/nostr.json")] [EnableCors(CorsPolicies.All)] [IgnoreAntiforgeryToken] [AllowAnonymous] public async Task GetUser([FromQuery] string name) { var result = await Get(name); return result.storeId is null ? NotFound() : Ok(new Nip5Response() { Names = new Dictionary() { {name, result.settings.PubKey} }, Relays = result.settings.Relays?.Any() is true ? new Dictionary() { {result.settings.PubKey, result.settings.Relays} } : null }); } }