towerops/setup_stripe_meter.exs
Graham McIntire ca7bb75472 fix: 11 critical security/correctness bugs from code audit
- C1: Replace innerHTML template literals with DOM textContent in sites_map.ts
- C2: Derive user_id from current_scope instead of client params in policy consent
- C3: Fix broken halt() in GDPR data export (orphaned _ = ... halt())
- C4: Add owner/admin authorization check to MembersController update/delete
- C5: Require HMAC signature on agent release webhook (was optional)
- C6: Fix Repo.all_by/2 (not a real Ecto function) → Repo.all with query
- C7: Move auth exemption to before_send callback in BruteForceProtection
- C8: Block </style>/<script injection in status page custom_css validation
- C9: Create secrets.example.yaml with placeholders; secrets.yaml already gitignored
- C10: Load Stripe key from STRIPE_SECRET_KEY env var instead of hardcoded value
- C13: Remove credentials from PubSub backup broadcast; channel resolves them

C11 (no force_ssl): by design — Cloudflared terminates TLS
C12 (device quota race): false positive — FOR UPDATE serializes correctly
2026-05-12 10:20:52 -05:00

103 lines
2.8 KiB
Elixir

#!/usr/bin/env elixir
# Setup script for Stripe billing meter
stripe_key = System.get_env("STRIPE_SECRET_KEY", "sk_test_use_your_own_key")
product_id = "prod_U6DpFdl21ftiDz"
IO.puts("Creating Stripe billing meter...")
# Create billing meter
form_data =
URI.encode_query(%{
"display_name" => "Devices Monitored",
"event_name" => "devices_monitored",
"default_aggregation[formula]" => "sum",
"value_settings[event_payload_key]" => "value"
})
{output, exit_code} =
System.cmd(
"curl",
[
"-X",
"POST",
"https://api.stripe.com/v1/billing/meters",
"-u",
"#{stripe_key}:",
"-H",
"Stripe-Version: 2024-11-20.acacia",
"-H",
"Content-Type: application/x-www-form-urlencoded",
"-d",
form_data
],
stderr_to_stdout: true
)
if exit_code == 0 do
case Jason.decode(output) do
{:ok, meter} ->
IO.puts("\n✓ Billing meter created successfully!")
IO.puts(" Meter ID: #{meter["id"]}")
IO.puts(" Display name: #{meter["display_name"]}")
IO.puts(" Event name: #{meter["event_name"]}")
IO.puts("\nNow creating metered price...")
# Create metered price linked to the meter
price_form =
URI.encode_query(%{
"product" => product_id,
"currency" => "usd",
"recurring[interval]" => "month",
"recurring[usage_type]" => "metered",
"recurring[meter]" => meter["id"],
"billing_scheme" => "per_unit",
# $1.00 in cents
"unit_amount" => "100",
"nickname" => "Per Device Monthly"
})
{price_output, price_exit} =
System.cmd(
"curl",
[
"-X",
"POST",
"https://api.stripe.com/v1/prices",
"-u",
"#{stripe_key}:",
"-H",
"Stripe-Version: 2024-11-20.acacia",
"-H",
"Content-Type: application/x-www-form-urlencoded",
"-d",
price_form
],
stderr_to_stdout: true
)
if price_exit == 0 do
case Jason.decode(price_output) do
{:ok, price} ->
IO.puts("\n✓ Price created successfully!")
IO.puts(" Price ID: #{price["id"]}")
IO.puts(" Amount: $#{price["unit_amount"] / 100}/device/month")
IO.puts("\n" <> String.duplicate("=", 60))
IO.puts("SETUP COMPLETE!")
IO.puts(String.duplicate("=", 60))
IO.puts("\nAdd these to your Kubernetes secret:")
IO.puts(" STRIPE_METER_ID=#{meter["id"]}")
IO.puts(" STRIPE_PRICE_ID=#{price["id"]}")
{:error, _} ->
IO.puts("\nError creating price: #{price_output}")
end
end
{:error, _} ->
IO.puts("\nError: #{output}")
end
else
IO.puts("\nCurl failed: #{output}")
end