kit_api 0.1.0 → 0.1.1

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: 4996d89da108c8784c235ec730e4d2b4137701d41c1942dfa3e6020d0f7c692e
4
- data.tar.gz: 95b2e1edd7d2f15effd72df4d75804202a6685b82e08fcd35f485b51219d4f63
3
+ metadata.gz: 8f29211b6943cfb1833bac057c9a467348e82209f61a466c0cc7185807196061
4
+ data.tar.gz: ddfd7c72c09e317fb691feb3886eb7f3d2333eaf3cd70b1559e43716625e1bc5
5
5
  SHA512:
6
- metadata.gz: '070426945ce384b443242bc1f72fe3546449fc5bfb05cde203b9bffe62177bdcf82992c5537a79e4881ad70cf0319429bae37f702921067bd35fc3eabb232364'
7
- data.tar.gz: 77670b26e8ec2cfd5006e18560f93e69b27199ef2edcd4f2ec2fb5be0cd329218be354b26ae1fc1b47090793e2e8e987375cacd78daddf37ca8c96a520ca4d79
6
+ metadata.gz: 46dd4f5676a5ce60556a5e03a59128b48902359b0409519047273e6b11bb55fd0b2cc27cc71776a7317de6c764e37c2c9b1cf7e7c50135170686be0edb78afc9
7
+ data.tar.gz: 49eda611a1c29fdf030e071594713cf1a3d40628fa5fcd8dc056b8c2db5931439df14a922d93776cf3ea27c5800940712fdf7e4f729ddd464b87d12f9281d8f5
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0 — Unreleased
3
+ ## 0.1.1 — Unreleased
4
+
5
+ - Security: URL-escape ids interpolated into request paths, so a hostile
6
+ id like `"42/unsubscribe"` can no longer splice the request into a
7
+ different endpoint.
8
+
9
+ ## 0.1.0 — 2026-09-26
4
10
 
5
11
  - Initial release: full Kit v4 API surface (subscribers, tags, custom fields,
6
12
  forms, sequences, sequence emails, broadcasts, account, purchases, segments,
@@ -8,7 +8,7 @@ module Kit
8
8
  end
9
9
 
10
10
  def broadcast(id)
11
- request(:get, "/broadcasts/#{id}")
11
+ request(:get, "/broadcasts/#{esc(id)}")
12
12
  end
13
13
 
14
14
  # send_at: nil leaves it a draft; subscriber_filter targets tags/segments.
@@ -17,15 +17,15 @@ module Kit
17
17
  end
18
18
 
19
19
  def update_broadcast(id, **attributes)
20
- request(:put, "/broadcasts/#{id}", body: attributes)
20
+ request(:put, "/broadcasts/#{esc(id)}", body: attributes)
21
21
  end
22
22
 
23
23
  def delete_broadcast(id)
24
- request(:delete, "/broadcasts/#{id}")
24
+ request(:delete, "/broadcasts/#{esc(id)}")
25
25
  end
26
26
 
27
27
  def broadcast_stats(id)
28
- request(:get, "/broadcasts/#{id}/stats")
28
+ request(:get, "/broadcasts/#{esc(id)}/stats")
29
29
  end
30
30
 
31
31
  # Filters: sent_after/before, status.
@@ -34,7 +34,7 @@ module Kit
34
34
  end
35
35
 
36
36
  def broadcast_clicks(id, **params)
37
- request(:get, "/broadcasts/#{id}/clicks", params: params)
37
+ request(:get, "/broadcasts/#{esc(id)}/clicks", params: params)
38
38
  end
39
39
  end
40
40
  end
@@ -12,11 +12,11 @@ module Kit
12
12
  end
13
13
 
14
14
  def update_custom_field(id, label:)
15
- request(:put, "/custom_fields/#{id}", body: { label: label })
15
+ request(:put, "/custom_fields/#{esc(id)}", body: { label: label })
16
16
  end
17
17
 
18
18
  def delete_custom_field(id)
19
- request(:delete, "/custom_fields/#{id}")
19
+ request(:delete, "/custom_fields/#{esc(id)}")
20
20
  end
21
21
 
22
22
  def bulk_create_custom_fields(custom_fields, callback_url: nil)
@@ -8,14 +8,14 @@ module Kit
8
8
  end
9
9
 
10
10
  def form_subscribers(id, **params)
11
- request(:get, "/forms/#{id}/subscribers", params: params)
11
+ request(:get, "/forms/#{esc(id)}/subscribers", params: params)
12
12
  end
13
13
 
14
14
  def add_subscriber_to_form(form_id, email_address: nil, subscriber_id: nil, referrer: nil)
15
15
  if subscriber_id
16
- request(:post, "/forms/#{form_id}/subscribers/#{subscriber_id}", body: { referrer: referrer }.compact)
16
+ request(:post, "/forms/#{esc(form_id)}/subscribers/#{esc(subscriber_id)}", body: { referrer: referrer }.compact)
17
17
  else
18
- request(:post, "/forms/#{form_id}/subscribers",
18
+ request(:post, "/forms/#{esc(form_id)}/subscribers",
19
19
  body: { email_address: email_address, referrer: referrer }.compact)
20
20
  end
21
21
  end
@@ -8,7 +8,7 @@ module Kit
8
8
  end
9
9
 
10
10
  def post(id)
11
- request(:get, "/posts/#{id}")
11
+ request(:get, "/posts/#{esc(id)}")
12
12
  end
13
13
  end
14
14
  end
@@ -8,7 +8,7 @@ module Kit
8
8
  end
9
9
 
10
10
  def purchase(id)
11
- request(:get, "/purchases/#{id}")
11
+ request(:get, "/purchases/#{esc(id)}")
12
12
  end
13
13
 
14
14
  # OAuth only. An existing transaction_id appends products instead of duplicating.
@@ -8,7 +8,7 @@ module Kit
8
8
  end
9
9
 
10
10
  def sequence(id)
11
- request(:get, "/sequences/#{id}")
11
+ request(:get, "/sequences/#{esc(id)}")
12
12
  end
13
13
 
14
14
  def create_sequence(name:, **attributes)
@@ -16,44 +16,44 @@ module Kit
16
16
  end
17
17
 
18
18
  def update_sequence(id, **attributes)
19
- request(:put, "/sequences/#{id}", body: attributes)
19
+ request(:put, "/sequences/#{esc(id)}", body: attributes)
20
20
  end
21
21
 
22
22
  def delete_sequence(id)
23
- request(:delete, "/sequences/#{id}")
23
+ request(:delete, "/sequences/#{esc(id)}")
24
24
  end
25
25
 
26
26
  def sequence_subscribers(id, **params)
27
- request(:get, "/sequences/#{id}/subscribers", params: params)
27
+ request(:get, "/sequences/#{esc(id)}/subscribers", params: params)
28
28
  end
29
29
 
30
30
  def add_subscriber_to_sequence(sequence_id, email_address: nil, subscriber_id: nil)
31
31
  if subscriber_id
32
- request(:post, "/sequences/#{sequence_id}/subscribers/#{subscriber_id}")
32
+ request(:post, "/sequences/#{esc(sequence_id)}/subscribers/#{esc(subscriber_id)}")
33
33
  else
34
- request(:post, "/sequences/#{sequence_id}/subscribers", body: { email_address: email_address })
34
+ request(:post, "/sequences/#{esc(sequence_id)}/subscribers", body: { email_address: email_address })
35
35
  end
36
36
  end
37
37
 
38
38
  def sequence_emails(sequence_id, **params)
39
- request(:get, "/sequences/#{sequence_id}/emails", params: params)
39
+ request(:get, "/sequences/#{esc(sequence_id)}/emails", params: params)
40
40
  end
41
41
 
42
42
  def sequence_email(sequence_id, id)
43
- request(:get, "/sequences/#{sequence_id}/emails/#{id}")
43
+ request(:get, "/sequences/#{esc(sequence_id)}/emails/#{esc(id)}")
44
44
  end
45
45
 
46
46
  def create_sequence_email(sequence_id, subject:, delay_value:, delay_unit:, **attributes)
47
- request(:post, "/sequences/#{sequence_id}/emails",
47
+ request(:post, "/sequences/#{esc(sequence_id)}/emails",
48
48
  body: { subject: subject, delay_value: delay_value, delay_unit: delay_unit, **attributes })
49
49
  end
50
50
 
51
51
  def update_sequence_email(sequence_id, id, **attributes)
52
- request(:put, "/sequences/#{sequence_id}/emails/#{id}", body: attributes)
52
+ request(:put, "/sequences/#{esc(sequence_id)}/emails/#{esc(id)}", body: attributes)
53
53
  end
54
54
 
55
55
  def delete_sequence_email(sequence_id, id)
56
- request(:delete, "/sequences/#{sequence_id}/emails/#{id}")
56
+ request(:delete, "/sequences/#{esc(sequence_id)}/emails/#{esc(id)}")
57
57
  end
58
58
  end
59
59
  end
@@ -8,7 +8,7 @@ module Kit
8
8
  end
9
9
 
10
10
  def snippet(id)
11
- request(:get, "/snippets/#{id}")
11
+ request(:get, "/snippets/#{esc(id)}")
12
12
  end
13
13
 
14
14
  # snippet_type: "inline" takes content:; "block" takes document_attributes:.
@@ -17,7 +17,7 @@ module Kit
17
17
  end
18
18
 
19
19
  def update_snippet(id, **attributes)
20
- request(:put, "/snippets/#{id}", body: attributes)
20
+ request(:put, "/snippets/#{esc(id)}", body: attributes)
21
21
  end
22
22
  end
23
23
  end
@@ -10,7 +10,7 @@ module Kit
10
10
  end
11
11
 
12
12
  def subscriber(id)
13
- request(:get, "/subscribers/#{id}")
13
+ request(:get, "/subscribers/#{esc(id)}")
14
14
  end
15
15
 
16
16
  # Upserts: an existing email_address gets its first_name updated.
@@ -19,11 +19,11 @@ module Kit
19
19
  end
20
20
 
21
21
  def update_subscriber(id, **attributes)
22
- request(:put, "/subscribers/#{id}", body: attributes)
22
+ request(:put, "/subscribers/#{esc(id)}", body: attributes)
23
23
  end
24
24
 
25
25
  def unsubscribe_subscriber(id)
26
- request(:post, "/subscribers/#{id}/unsubscribe")
26
+ request(:post, "/subscribers/#{esc(id)}/unsubscribe")
27
27
  end
28
28
 
29
29
  def bulk_create_subscribers(subscribers, callback_url: nil)
@@ -31,23 +31,23 @@ module Kit
31
31
  end
32
32
 
33
33
  def subscriber_stats(id, **params)
34
- request(:get, "/subscribers/#{id}/stats", params: params)
34
+ request(:get, "/subscribers/#{esc(id)}/stats", params: params)
35
35
  end
36
36
 
37
37
  def subscriber_tags(id, **params)
38
- request(:get, "/subscribers/#{id}/tags", params: params)
38
+ request(:get, "/subscribers/#{esc(id)}/tags", params: params)
39
39
  end
40
40
 
41
41
  def pin_subscriber_location(id, **location)
42
- request(:post, "/subscribers/#{id}/location", body: { location: location })
42
+ request(:post, "/subscribers/#{esc(id)}/location", body: { location: location })
43
43
  end
44
44
 
45
45
  def update_subscriber_location(id, **location)
46
- request(:patch, "/subscribers/#{id}/location", body: { location: location })
46
+ request(:patch, "/subscribers/#{esc(id)}/location", body: { location: location })
47
47
  end
48
48
 
49
49
  def delete_subscriber_location(id)
50
- request(:delete, "/subscribers/#{id}/location")
50
+ request(:delete, "/subscribers/#{esc(id)}/location")
51
51
  end
52
52
  end
53
53
  end
@@ -12,26 +12,26 @@ module Kit
12
12
  end
13
13
 
14
14
  def update_tag(id, name:)
15
- request(:put, "/tags/#{id}", body: { name: name })
15
+ request(:put, "/tags/#{esc(id)}", body: { name: name })
16
16
  end
17
17
 
18
18
  def tag_subscribers(id, **params)
19
- request(:get, "/tags/#{id}/subscribers", params: params)
19
+ request(:get, "/tags/#{esc(id)}/subscribers", params: params)
20
20
  end
21
21
 
22
22
  def tag_subscriber(tag_id, email_address: nil, subscriber_id: nil)
23
23
  if subscriber_id
24
- request(:post, "/tags/#{tag_id}/subscribers/#{subscriber_id}")
24
+ request(:post, "/tags/#{esc(tag_id)}/subscribers/#{esc(subscriber_id)}")
25
25
  else
26
- request(:post, "/tags/#{tag_id}/subscribers", body: { email_address: email_address })
26
+ request(:post, "/tags/#{esc(tag_id)}/subscribers", body: { email_address: email_address })
27
27
  end
28
28
  end
29
29
 
30
30
  def untag_subscriber(tag_id, email_address: nil, subscriber_id: nil)
31
31
  if subscriber_id
32
- request(:delete, "/tags/#{tag_id}/subscribers/#{subscriber_id}")
32
+ request(:delete, "/tags/#{esc(tag_id)}/subscribers/#{esc(subscriber_id)}")
33
33
  else
34
- request(:delete, "/tags/#{tag_id}/subscribers", params: { email_address: email_address })
34
+ request(:delete, "/tags/#{esc(tag_id)}/subscribers", params: { email_address: email_address })
35
35
  end
36
36
  end
37
37
 
@@ -8,7 +8,7 @@ module Kit
8
8
  end
9
9
 
10
10
  def webhook(id)
11
- request(:get, "/webhook_endpoints/#{id}")
11
+ request(:get, "/webhook_endpoints/#{esc(id)}")
12
12
  end
13
13
 
14
14
  # The signing secret comes back in plaintext only on this response.
@@ -17,19 +17,19 @@ module Kit
17
17
  end
18
18
 
19
19
  def update_webhook(id, **attributes)
20
- request(:put, "/webhook_endpoints/#{id}", body: attributes)
20
+ request(:put, "/webhook_endpoints/#{esc(id)}", body: attributes)
21
21
  end
22
22
 
23
23
  def delete_webhook(id)
24
- request(:delete, "/webhook_endpoints/#{id}")
24
+ request(:delete, "/webhook_endpoints/#{esc(id)}")
25
25
  end
26
26
 
27
27
  def rotate_webhook_secret(id, force: nil)
28
- request(:post, "/webhook_endpoints/#{id}/rotate_secret", body: { force: force }.compact)
28
+ request(:post, "/webhook_endpoints/#{esc(id)}/rotate_secret", body: { force: force }.compact)
29
29
  end
30
30
 
31
31
  def revoke_previous_webhook_secret(id)
32
- request(:post, "/webhook_endpoints/#{id}/revoke_previous_secret")
32
+ request(:post, "/webhook_endpoints/#{esc(id)}/revoke_previous_secret")
33
33
  end
34
34
  end
35
35
  end
data/lib/kit/client.rb CHANGED
@@ -62,6 +62,10 @@ module Kit
62
62
  end
63
63
 
64
64
  private
65
+ # Ids land in URL paths; escaping keeps a hostile "42/unsubscribe"
66
+ # from splicing itself into a different endpoint.
67
+ def esc(id) = URI.encode_www_form_component(id.to_s)
68
+
65
69
  def tag_id(name)
66
70
  @tag_ids ||= {}
67
71
  @tag_ids[name] ||= begin
data/lib/kit/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kit
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kit_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike D