Fix: Archived invoices shouldn't be browsable by non authenticated users (#6588)

* fix: return 403 when unauthenticated user accesses an archived invoice receipt

* refactor: simplify archived invoice access check with pattern matching

* Return 404 for unauthorized users accessing archived invoice

Co-authored-by: d11n <mail@dennisreimann.de>

* feat: add archived invoice validation for unauthenticated access in Checkout and GetStatus

* test: add test case for unauthorized access to archived invoice returning not found

* test: add unauthorized checkout test for archived invoice

* Commenting code and adding test case for GetStatus

---------

Co-authored-by: d11n <mail@dennisreimann.de>
Co-authored-by: rockstardev <5191402+rockstardev@users.noreply.github.com>
This commit is contained in:
ThiagoOyo 2025-03-06 05:45:53 -03:00 committed by GitHub
parent 7b4fd0e40c
commit 9bff84f90f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 0 deletions

View file

@ -888,6 +888,28 @@ namespace BTCPayServer.Tests
Assert.DoesNotContain("invoice-unsettled", s.Driver.PageSource); Assert.DoesNotContain("invoice-unsettled", s.Driver.PageSource);
Assert.DoesNotContain("invoice-processing", s.Driver.PageSource); Assert.DoesNotContain("invoice-processing", s.Driver.PageSource);
}); });
// ensure archived invoices are not accessible for logged out users
await s.Server.PayTester.InvoiceRepository.ToggleInvoiceArchival(i, true);
s.Logout();
await s.Driver.Navigate().GoToUrlAsync(s.Driver.Url + $"/i/{i}/receipt");
TestUtils.Eventually(() =>
{
Assert.Contains("Page not found", s.Driver.Title, StringComparison.OrdinalIgnoreCase);
});
await s.Driver.Navigate().GoToUrlAsync(s.Driver.Url + $"i/{i}");
TestUtils.Eventually(() =>
{
Assert.Contains("Page not found", s.Driver.Title, StringComparison.OrdinalIgnoreCase);
});
await s.Driver.Navigate().GoToUrlAsync(s.Driver.Url + $"i/{i}/status");
TestUtils.Eventually(() =>
{
Assert.Contains("Page not found", s.Driver.Title, StringComparison.OrdinalIgnoreCase);
});
} }
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]

View file

@ -198,6 +198,9 @@ namespace BTCPayServer.Controllers
var store = await _StoreRepository.GetStoreByInvoiceId(i.Id); var store = await _StoreRepository.GetStoreByInvoiceId(i.Id);
if (store is null) if (store is null)
return NotFound(); return NotFound();
if (!await ValidateAccessForArchivedInvoice(i))
return NotFound();
var receipt = InvoiceDataBase.ReceiptOptions.Merge(store.GetStoreBlob().ReceiptOptions, i.ReceiptOptions); var receipt = InvoiceDataBase.ReceiptOptions.Merge(store.GetStoreBlob().ReceiptOptions, i.ReceiptOptions);
if (receipt.Enabled is not true) if (receipt.Enabled is not true)
@ -727,6 +730,9 @@ namespace BTCPayServer.Controllers
if (invoice == null) if (invoice == null)
return null; return null;
if (!await ValidateAccessForArchivedInvoice(invoice))
return null;
var store = await _StoreRepository.FindStore(invoice.StoreId); var store = await _StoreRepository.FindStore(invoice.StoreId);
if (store == null) if (store == null)
return null; return null;
@ -1000,6 +1006,10 @@ namespace BTCPayServer.Controllers
var invoice = await _InvoiceRepository.GetInvoice(invoiceId); var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
if (invoice == null || invoice.Status == InvoiceStatus.Settled || invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired) if (invoice == null || invoice.Status == InvoiceStatus.Settled || invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired)
return NotFound(); return NotFound();
if (!await ValidateAccessForArchivedInvoice(invoice))
return NotFound();
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
CompositeDisposable leases = new CompositeDisposable(); CompositeDisposable leases = new CompositeDisposable();
try try
@ -1314,5 +1324,12 @@ namespace BTCPayServer.Controllers
}); });
return RedirectToAction(nameof(ListInvoices), new { storeId }); return RedirectToAction(nameof(ListInvoices), new { storeId });
} }
private async Task<bool> ValidateAccessForArchivedInvoice(InvoiceEntity invoice)
{
if (!invoice.Archived) return true;
var authorizationResult = await _authorizationService.AuthorizeAsync(User, invoice.StoreId, Policies.CanViewInvoices);
return authorizationResult.Succeeded;
}
} }
} }