better_auth-stripe 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d60c9b90345ede9ff5fda0bc9420f0f9d2cc415312e0a587daa1e2aa451ec109
4
- data.tar.gz: cdd8dfe8742cdc419eb0c94082ad9d5e30b9d6466bc938253cd54267e1e547fc
3
+ metadata.gz: 1858dc05175fb2101ce2afb5f74a3e7de80a9b88c96a7980fbd7b6bfcac6afcc
4
+ data.tar.gz: f3f683695cb0f73a2f8d5aca655f9cd8b040fa97e26fadac1300096488cc783f
5
5
  SHA512:
6
- metadata.gz: ca83e6cbfc4c7ea4dc1f488ac3a91fe842a5fb19cfdd3754d9c334f65b9eb5e8ef1d01ba0c5c350f48a037a06a3e02665906d32f49b60a294878f2657ef0f66b
7
- data.tar.gz: 768570c25480a3549a1c85a795cfce82c7a0462ef8c15687619fd682c4aaa4ce9d3193e8490bc6496244ef1d88937a232ca6d3a8136b26223f199018755f02ff
6
+ metadata.gz: fbb99518f0ad2849d066462f0fa1cbfb0a6451c741eac7aa45d86a6176b03c03543eccd58dc60ecd5df25898c10739d9ce8b033446fc8264822c1754450b4ff1
7
+ data.tar.gz: f876d15cc9690af96b0b864665a767bd6dddeac4ed8508bede47b78e874a9917e0d032bbc43f77980cc2d27bab89f5f1ea726f60ab97d13cb4ed6734bd170006
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.7.0] - 2026-05-05
6
+
7
+ - Changed Stripe webhooks to reject requests when the configured Stripe client does not expose `webhooks.construct_event_async` or `webhooks.construct_event`, preventing unverified payload processing.
8
+ - Hardened Stripe subscription route middleware and webhook origin handling with regression coverage.
9
+
5
10
  ## [0.6.0] - 2026-05-02
6
11
 
7
12
  - Modularized the Stripe plugin into upstream-aligned client, schema, middleware, hooks, route, metadata, type, and utility modules while keeping the existing public facade.
@@ -47,6 +47,22 @@ module BetterAuth
47
47
 
48
48
  nil
49
49
  end
50
+
51
+ def validate_trusted_url!(ctx, value, label)
52
+ return if value.nil? || value.to_s.empty?
53
+
54
+ validation_value = value.to_s.gsub("{CHECKOUT_SESSION_ID}", "checkout_session_id")
55
+ return if ctx.context.trusted_origin?(validation_value, allow_relative_paths: true)
56
+
57
+ raise APIError.new("FORBIDDEN", message: "Invalid #{label}")
58
+ end
59
+
60
+ def validate_trusted_urls!(ctx, source, mapping)
61
+ body = BetterAuth::Plugins.normalize_hash(source || {})
62
+ mapping.each do |key, label|
63
+ validate_trusted_url!(ctx, body[key], label)
64
+ end
65
+ end
50
66
  end
51
67
  end
52
68
  end
@@ -10,6 +10,7 @@ module BetterAuth
10
10
  BetterAuth::Endpoint.new(path: "/subscription/cancel", method: "POST") do |ctx|
11
11
  session = BetterAuth::Routes.current_session(ctx)
12
12
  body = BetterAuth::Plugins.normalize_hash(ctx.body)
13
+ BetterAuth::Stripe::Middleware.validate_trusted_urls!(ctx, body, return_url: "returnUrl")
13
14
  customer_type = BetterAuth::Plugins.stripe_customer_type!(body)
14
15
  reference_id = BetterAuth::Plugins.stripe_reference_id!(ctx, session, customer_type, body[:reference_id], config)
15
16
  BetterAuth::Plugins.stripe_authorize_reference!(ctx, session, reference_id, "cancel-subscription", customer_type, BetterAuth::Plugins.stripe_subscription_options(config), explicit: body.key?(:reference_id))
@@ -10,6 +10,7 @@ module BetterAuth
10
10
  BetterAuth::Endpoint.new(path: "/subscription/cancel/callback", method: "GET") do |ctx|
11
11
  query = BetterAuth::Plugins.normalize_hash(ctx.query)
12
12
  callback = query[:callback_url] || "/"
13
+ BetterAuth::Stripe::Middleware.validate_trusted_url!(ctx, callback, "callbackURL")
13
14
  unless query[:subscription_id]
14
15
  raise ctx.redirect(BetterAuth::Plugins.stripe_url(ctx, callback))
15
16
  end
@@ -10,6 +10,7 @@ module BetterAuth
10
10
  BetterAuth::Endpoint.new(path: "/subscription/billing-portal", method: "POST") do |ctx|
11
11
  session = BetterAuth::Routes.current_session(ctx)
12
12
  body = BetterAuth::Plugins.normalize_hash(ctx.body)
13
+ BetterAuth::Stripe::Middleware.validate_trusted_urls!(ctx, body, return_url: "returnUrl")
13
14
  customer_type = BetterAuth::Plugins.stripe_customer_type!(body)
14
15
  reference_id = BetterAuth::Plugins.stripe_reference_id!(ctx, session, customer_type, body[:reference_id], config)
15
16
  BetterAuth::Plugins.stripe_authorize_reference!(ctx, session, reference_id, "billing-portal", customer_type, BetterAuth::Plugins.stripe_subscription_options(config), explicit: body.key?(:reference_id))
@@ -7,23 +7,29 @@ module BetterAuth
7
7
  module_function
8
8
 
9
9
  def endpoint(config)
10
- BetterAuth::Endpoint.new(path: "/stripe/webhook", method: "POST") do |ctx|
10
+ BetterAuth::Endpoint.new(path: "/stripe/webhook", method: "POST", disable_body: true) do |ctx|
11
11
  signature = ctx.headers["stripe-signature"]
12
12
  raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Stripe::ERROR_CODES.fetch("STRIPE_SIGNATURE_NOT_FOUND")) if signature.to_s.empty?
13
13
 
14
14
  raise BetterAuth::APIError.new("INTERNAL_SERVER_ERROR", message: BetterAuth::Stripe::ERROR_CODES.fetch("STRIPE_WEBHOOK_SECRET_NOT_FOUND")) if config[:stripe_webhook_secret].to_s.empty?
15
15
 
16
+ payload = ctx.raw_body.to_s.empty? ? ctx.body : ctx.raw_body
16
17
  event = begin
17
- if BetterAuth::Plugins.stripe_client(config).respond_to?(:webhooks)
18
- webhooks = BetterAuth::Plugins.stripe_client(config).webhooks
19
- if webhooks.respond_to?(:construct_event_async)
20
- webhooks.construct_event_async(ctx.body, signature, config[:stripe_webhook_secret])
21
- else
22
- webhooks.construct_event(ctx.body, signature, config[:stripe_webhook_secret])
23
- end
18
+ client = BetterAuth::Plugins.stripe_client(config)
19
+ webhooks = client.webhooks if client.respond_to?(:webhooks)
20
+ unless webhooks
21
+ raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Stripe::ERROR_CODES.fetch("FAILED_TO_CONSTRUCT_STRIPE_EVENT"))
22
+ end
23
+
24
+ if webhooks.respond_to?(:construct_event_async)
25
+ webhooks.construct_event_async(payload, signature, config[:stripe_webhook_secret])
26
+ elsif webhooks.respond_to?(:construct_event)
27
+ webhooks.construct_event(payload, signature, config[:stripe_webhook_secret])
24
28
  else
25
- ctx.body
29
+ raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Stripe::ERROR_CODES.fetch("FAILED_TO_CONSTRUCT_STRIPE_EVENT"))
26
30
  end
31
+ rescue BetterAuth::APIError
32
+ raise
27
33
  rescue
28
34
  raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Stripe::ERROR_CODES.fetch("FAILED_TO_CONSTRUCT_STRIPE_EVENT"))
29
35
  end
@@ -10,6 +10,7 @@ module BetterAuth
10
10
  BetterAuth::Endpoint.new(path: "/subscription/success", method: "GET") do |ctx|
11
11
  query = BetterAuth::Plugins.normalize_hash(ctx.query)
12
12
  callback = query[:callback_url] || "/"
13
+ BetterAuth::Stripe::Middleware.validate_trusted_url!(ctx, callback, "callbackURL")
13
14
  checkout_session_id = query[:checkout_session_id]
14
15
  subscription_id = query[:subscription_id]
15
16
  if checkout_session_id
@@ -10,6 +10,7 @@ module BetterAuth
10
10
  BetterAuth::Endpoint.new(path: "/subscription/upgrade", method: "POST") do |ctx|
11
11
  session = BetterAuth::Routes.current_session(ctx)
12
12
  body = BetterAuth::Plugins.normalize_hash(ctx.body)
13
+ BetterAuth::Stripe::Middleware.validate_trusted_urls!(ctx, body, success_url: "successUrl", cancel_url: "cancelUrl", return_url: "returnUrl")
13
14
  subscription_options = BetterAuth::Plugins.stripe_subscription_options(config)
14
15
  customer_type = BetterAuth::Plugins.stripe_customer_type!(body)
15
16
  reference_id = BetterAuth::Plugins.stripe_reference_id!(ctx, session, customer_type, body[:reference_id], config)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BetterAuth
4
4
  module Stripe
5
- VERSION = "0.6.2"
5
+ VERSION = "0.7.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_auth-stripe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Sala