* fix(payment): reflect highest active plan across overlapping Stripe subscriptions
When a user upgrades Plus to Pro, both subscriptions stay active until the old
one is cancelled. Each subscription webhook overwrote plans.plan with only that
event's plan, so whichever webhook arrived last won and could downgrade the
account back to plus.
Derive plans.plan from the highest active (or trialing) subscription via a new
getHighestActivePlan helper, used by createOrUpdateSubscription and by the
cancellation handler so a still-active higher plan is preserved instead of
dropping to free.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(payment): add opt-in live Stripe test for getHighestActivePlan
Skipped by default (CI included); runs only when STRIPE_SECRET_KEY and
STRIPE_TEST_CUSTOMER_ID are set, so it can be exercised locally against a real
customer with overlapping subscriptions. The module under test is imported
dynamically so the file stays import-safe while skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server-side hardening for three reported web advisories:
- OPDS proxy (/api/opds/proxy): add http(s) scheme allowlist, internal/loopback/
link-local host blocklist, and manual per-hop redirect re-validation so a
public URL can't redirect into an internal address. Move isBlockedHost into
the shared src/utils/network.ts as the canonical blocklist and reimplement
isLanAddress to delegate to it (also tightens the /api/kosync LAN check);
fetch-url.ts re-exports it. (GHSA-c7mm-g2j2-98cx, GHSA-5g3f-mq2c-j65v)
- Storage upload (/api/storage/upload): validate the client-supplied fileName
with a new isSafeObjectKeyName helper before building the object key, so a
name can't escape the caller's own prefix. (GHSA-mfmj-2frf-vhgw)
- Stripe (/api/stripe/check): bind the entitlement to the session owner —
reject a Checkout Session whose metadata.userId differs from the authenticated
caller. (GHSA-pv88-3727-j7v8)
Unit tests added for each path; full suite + lint green. The Tauri-native
advisory (GHSA-55vr-pvq5-6fmg) is handled in a separate change.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(deps): update stripe
* fix: filter and sort not affecting purchases
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* refactor: rename restoredPurchases to restoredSubscriptions
The filter excludes one-time purchases, so the array contains only
subscriptions. Naming reflects that.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>