Fix: Can't upgrade/downgrade a Lifetime subscription (#7194)
What changed, and why it matters
This commit fixes a JavaScript crash in the subscription portal. When a user had a lifetime subscription, the page did not show an auto-renewal checkbox, but the script still tried to attach an event listener to it. That caused a browser error that blocked the rest of the page's JavaScript, making it impossible to upgrade or downgrade the subscription. The fix simply checks whether the checkbox exists before attaching the listener. There is no direct security vulnerability here; it is a functional bug with a minor denial-of-service-like effect on the UI.
No security action required beyond normal regression testing. Verify that lifetime subscribers can now open the change-plan modal and that other subscription types still auto-submit the renewal form correctly.
Security signals we found
Client-side null-reference causing script failure
UI functionality blocked for a specific subscription state
No authentication, authorization, injection, or data-integrity issues visible in diff
Evidence from the diff
The change in SubscriberPortal.cshtml wraps the document.querySelector(‘#autoRenewal’) event-listener registration in an existence check. Previously, the code unconditionally called addEventListener on a null element for lifetime subscriptions (which omit the auto-renewal control), throwing a TypeError and halting subsequent script execution. This prevented the change-plan modal logic from running. The UnitTest1.cs change removes a timeout attribute from an unrelated wallet-rescan test and appears incidental.
Changed components
BTCPayServer/Plugins/Subscriptions/Views/UISubscriberPortal/SubscriberPortal.cshtmlSubscription portal UI for lifetime subscribersInspect captured patch +11 / −7
diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs
index d81d87a..c5be1d4 100644
--- a/BTCPayServer.Tests/UnitTest1.cs
+++ b/BTCPayServer.Tests/UnitTest1.cs
@@ -535,7 +535,7 @@ namespace BTCPayServer.Tests
}
}
- [Fact(Timeout = LongRunningTestTimeout)]
+ [Fact]
[Trait("Integration", "Integration")]
public async Task CanRescanWallet()
{
diff --git a/BTCPayServer/Plugins/Subscriptions/Views/UISubscriberPortal/SubscriberPortal.cshtml b/BTCPayServer/Plugins/Subscriptions/Views/UISubscriberPortal/SubscriberPortal.cshtml
index 47ce014..c37cab3 100644
--- a/BTCPayServer/Plugins/Subscriptions/Views/UISubscriberPortal/SubscriberPortal.cshtml
+++ b/BTCPayServer/Plugins/Subscriptions/Views/UISubscriberPortal/SubscriberPortal.cshtml
@@ -650,13 +650,17 @@
</text>
}
- document.querySelector("#autoRenewal").addEventListener('change', function (e) {
- e.preventDefault();
- fetch(this.form.action, {
- method: 'POST',
- body: new FormData(this.form)
+ var autoRenewal = document.querySelector("#autoRenewal");
+ if (autoRenewal)
+ {
+ autoRenewal.addEventListener('change', function (e) {
+ e.preventDefault();
+ fetch(this.form.action, {
+ method: 'POST',
+ body: new FormData(this.form)
+ });
});
- });
+ }
(function () {
const changePlanModal = document.getElementById('changePlanModal');
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.