Files
member-console/internal/server/member_switch_gate_test.go
T
cgalo5758 0b28a9dc29 Remediate security audit findings
- Replace gorilla/csrf with net/http CrossOriginProtection
- Require valkey-password and add TLS options for session store
- End session at /logout and revoke refresh tokens
- Re-derive identity and roles from provider every five minutes
- Process each Stripe webhook event in its own Temporal workflow
- Give each outbox entry its own workflow with Temporal retries
- Guard against stale Stripe events with provider timestamps
- Derive transport security from base-url scheme
2026-09-09 13:25:43 -05:00

63 lines
2.5 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
package server
import (
"context"
"errors"
"io"
"log/slog"
"testing"
"git.coopcloud.tech/wiki-cafe/member-console/internal/billing"
)
// Finding 12 of the 2026-09 security audit: checkout refused a product that
// failed the member gate, the switch path did not, so a member who knew a
// draft tier's price id could move onto it. The switch now holds its target
// to the same gate before any subscription is touched. The querier stubs
// only the two reads the gate makes; anything further panics on the nil
// interface, which is the proof that a refusal stops there.
func TestSwitchTargetRefusesAProductOutsideTheMemberGate(t *testing.T) {
cases := []struct {
name string
product billing.Product
}{
{"draft product", billing.Product{ProductID: "prod-draft", LifecycleStatus: "draft", IsActive: true, IsPublic: true}},
{"retired product", billing.Product{ProductID: "prod-retired", LifecycleStatus: "retired", IsActive: true, IsPublic: true}},
{"inactive product", billing.Product{ProductID: "prod-inactive", LifecycleStatus: "published", IsActive: false, IsPublic: true}},
{"non-public product", billing.Product{ProductID: "prod-internal", LifecycleStatus: "published", IsActive: true, IsPublic: false}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
h := &MemberProductsHandler{
BillingQ: fakeCheckoutQuerier{
price: billing.Price{PriceID: "price-1", ProductID: tc.product.ProductID, IsActive: true},
product: tc.product,
},
Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
}
err := h.switchTargetOpenToMembers(context.Background(), "price-1")
if !errors.Is(err, errPlanNotOpenToMembers) {
t.Fatalf("err = %v, want errPlanNotOpenToMembers", err)
}
})
}
}
// The control: a published, active, public target passes, so the gate
// refuses the draft and not every switch.
func TestSwitchTargetAdmitsAPublishedProduct(t *testing.T) {
h := &MemberProductsHandler{
BillingQ: fakeCheckoutQuerier{
price: billing.Price{PriceID: "price-1", ProductID: "prod-1", IsActive: true},
product: billing.Product{ProductID: "prod-1", LifecycleStatus: "published", IsActive: true, IsPublic: true},
},
Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
}
if err := h.switchTargetOpenToMembers(context.Background(), "price-1"); err != nil {
t.Fatalf("a published product must pass the gate, got %v", err)
}
}