stitches 5.1.0.RC4 → 5.1.0.RC5

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: 5e1a4aefc1d0249b7b5c8caf2eb06aa6e0866afef4f1d4564d63efa239ef80ce
4
- data.tar.gz: 322ab581e74df9bb933d8a1e23d2dc3973c6e3494ebfac23424f16c25cbf589a
3
+ metadata.gz: f589f4d3f2537a4f9b17328ad88ca1b89031bd80309eddc63657b2799fb7e0e9
4
+ data.tar.gz: 1c51e01a86ee6ad3fa70f7e7e69b9a700bfc2ea87040f8b9c059e52648a70632
5
5
  SHA512:
6
- metadata.gz: 5da26d78de12b20c29f2cff453dae8b1b1712ab3f0bbcd373291082eeee1feae3f22dcfb841c512f6a351243f10894b1c91dbc6c5c58400c182be9c0e96ee93c
7
- data.tar.gz: 1a2ef89f2e74dfd9478158dfa747c19989de5f32425363f77ae428058468e57d934e29decae0fb1b1bc39a60b1b18abb97c8c82bfd3dfde73ac0b449dc13c995
6
+ metadata.gz: f5e2b42fa7a6609e8dc8e6bec1cf863e4c2e0e7d5bc47ae581187ce7ae2b15c672ec407fb20d07fe3829807e0ec976cbb7b56eeb47d47303352d3807ffd99b40
7
+ data.tar.gz: 766a73badd49a7aee8c3b16635230082dc0fb7e39cba5e20ef0e7acaaab42641874dd2348b233adcfa7f15aee883bef42965f82d3a7d46187f04799a1e632939
data/README.md CHANGED
@@ -42,73 +42,77 @@ Stitches.configure do |config|
42
42
  end
43
43
  ```
44
44
 
45
- ### Caller Identification via `X-StitchFix-Calling-Service`
45
+ ### Caller Identification
46
46
 
47
47
  When API key auth is disabled, services lose the ability to identify which
48
- internal service is calling them. The `Stitches::CallingServiceName` concern
49
- provides a replacement that works with or without API keys enabled.
48
+ internal service is calling them. Stitches 5.1+ provides two mechanisms that
49
+ work together to restore this one automatic, one opt-in.
50
50
 
51
- **Include the concern in your API controller:**
51
+ The calling service header name defaults to `X-StitchFix-Calling-Service` but
52
+ is configurable via `Stitches.configuration.calling_service_header`.
52
53
 
53
- ```ruby
54
- class Api::ApiController < ActionController::API
55
- include Stitches::CallingServiceName
54
+ #### CallingServiceMiddleware (automatic)
56
55
 
57
- # Replace api_client.name with calling_service_name anywhere you need
58
- # to know who the caller is (stats, routing, updated_by, etc.)
59
- end
60
- ```
56
+ `CallingServiceMiddleware` is registered via the railtie and runs after the
57
+ `ApiKey` middleware. When no auth middleware has populated the caller identity
58
+ env var (`Stitches.configuration.env_var_to_hold_api_client`), it reads the
59
+ calling service header and populates it with a `CallingServiceClient` struct.
61
60
 
62
- For controllers that don't inherit from your ApiController (e.g. Devise
63
- controllers), include the concern directly:
61
+ **This means existing code that reads the caller identity object's `.name`
62
+ continues to work with no changes** — the value now comes from the header
63
+ instead of a DB lookup.
64
64
 
65
- ```ruby
66
- class Api::V1::SessionsController < Devise::SessionsController
67
- include Stitches::CallingServiceName
68
- end
69
- ```
65
+ `CallingServiceClient` implements:
66
+ - `.name` the header value (e.g. "my-app")
67
+ - `.id` — nil
68
+ - `.key` — nil
70
69
 
71
- **Resolution order:**
70
+ **Resolution order (for `request.env[env_var_to_hold_api_client]`):**
72
71
 
73
- 1. `X-StitchFix-Calling-Service` request header (preferred)
74
- 2. `api_client&.name` the stitches-authenticated ApiClient (works when API keys are enabled)
75
- 3. `""` (empty string) fallback when neither is available
72
+ 1. If the `ApiKey` middleware authenticated a key → `ApiClient` record (has `.name`, `.id`, `.key`)
73
+ 2. If JWT or other auth middleware set the env var that object (e.g. a user)
74
+ 3. If neither ran, but `X-StitchFix-Calling-Service` header is present `CallingServiceClient` struct
75
+ 4. If nothing → nil
76
76
 
77
- This is backwards compatible. When `disable_api_key_support` is false, the
78
- stitches middleware still populates `api_client`, so the fallback returns the
79
- authenticated name. When true, callers identify themselves via the header.
77
+ **Middleware ordering:** `ApiKey` `CallingServiceMiddleware` `ValidMimeType`
80
78
 
81
- **Client-side setup:**
79
+ #### CallingServiceName concern (opt-in)
82
80
 
83
- Clients using `stitchfix-api_client` (v5.1+) automatically send this header.
84
- The value is derived from the `app_name` config option or `ENV["APP_NAME"]`:
81
+ For cases where you want strictly the header value (e.g. metrics tags where
82
+ you never want a human user's name), include the concern:
85
83
 
86
84
  ```ruby
87
- MyServiceClient.new(
88
- endpoint: "https://my-service.internal",
89
- app_name: "kingmob" # sent as X-StitchFix-Calling-Service
90
- )
85
+ class Api::ApiController < ActionController::API
86
+ include Stitches::CallingServiceName
87
+ end
88
+
89
+ # Returns the header value only, empty string if absent:
90
+ calling_service_name # => "my-app" or ""
91
91
  ```
92
92
 
93
- If `app_name` is not set, `ENV["APP_NAME"]` is used (typically set in
94
- production deployments). If neither is available, the header is not sent.
93
+ #### Configuration
94
+
95
+ The header name is configurable (defaults to `X-StitchFix-Calling-Service`):
96
+
97
+ ```ruby
98
+ Stitches.configure do |config|
99
+ config.calling_service_header = "X-My-Custom-Header"
100
+ end
101
+ ```
95
102
 
96
103
  #### Security considerations
97
104
 
98
105
  `X-StitchFix-Calling-Service` is a **self-declared, unsigned header**. Any
99
106
  caller that can reach your service can set it to any value. This means:
100
107
 
101
- - **Do not use `calling_service_name` as a sole authorization mechanism for
102
- sensitive operations** (e.g. granting admin tokens, bypassing verification
103
- flows) unless the network layer guarantees that only the legitimate service
104
- can reach the endpoint.
108
+ - **Do not use it as a sole authorization mechanism** for sensitive operations
109
+ unless the network layer guarantees that only the legitimate service can
110
+ reach the endpoint.
105
111
  - **Strip this header at public ingress points** (Traefik, ALB, API gateway)
106
112
  to prevent external callers from spoofing internal service identities.
107
- Internal-only traffic should be the only source of this header.
108
- - **This header replaces API key-based identity, not authorization.** API
109
- keys provided a weak form of authentication (possession of a shared secret).
110
- This header provides identification only. If you need to verify the caller's
111
- identity cryptographically, use mTLS or a service mesh AuthorizationPolicy.
113
+ - **This header replaces API key-based identity, not authorization.** It
114
+ provides identification only. If you need to verify the caller's identity
115
+ cryptographically, use mTLS or a service mesh AuthorizationPolicy.
112
116
  - **Safe uses:** stats tagging, logging, `updated_by` audit fields, routing
113
117
  hints, non-security-critical behavioral branching.
114
118
  - **Unsafe without network enforcement:** access control decisions, privilege
@@ -1,8 +1,8 @@
1
1
  module Stitches
2
2
  # Lightweight stand-in for ApiClient when the caller is identified by
3
- # the X-StitchFix-Calling-Service header rather than an API key lookup.
4
- # Implements the same interface (.name, .id, .key) so existing code
5
- # that reads api_client.name continues to work.
3
+ # the calling service header rather than an API key lookup. Implements
4
+ # the same interface (.name, .id, .key) so existing code that reads
5
+ # the caller identity object continues to work.
6
6
  CallingServiceClient = Struct.new(:name) do
7
7
  def id
8
8
  nil
@@ -1,10 +1,10 @@
1
1
  require_relative 'calling_service_client'
2
2
 
3
3
  module Stitches
4
- # Rack middleware that populates the api_client env var from the
5
- # X-StitchFix-Calling-Service header when no other auth middleware
6
- # has already set it. This allows existing code that reads
7
- # api_client.name to work transparently after API keys are disabled.
4
+ # Rack middleware that populates the caller identity env var from the
5
+ # calling service header when no other auth middleware has already set
6
+ # it. This allows existing code that reads the caller identity object
7
+ # to work transparently after API keys are disabled.
8
8
  class CallingServiceMiddleware
9
9
  def initialize(app)
10
10
  @app = app
@@ -15,7 +15,6 @@ module Stitches
15
15
 
16
16
  unless env[client_key]
17
17
  header_name = Stitches.configuration.calling_service_header
18
- header_name = CallingServiceName::DEFAULT_CALLING_SERVICE_HEADER unless header_name.present?
19
18
  rack_key = "HTTP_#{header_name.upcase.tr('-', '_')}"
20
19
 
21
20
  if (name = env[rack_key]).present?
@@ -1,17 +1,8 @@
1
1
  module Stitches
2
2
  module CallingServiceName
3
- DEFAULT_CALLING_SERVICE_HEADER = "X-StitchFix-Calling-Service"
4
-
5
3
  def calling_service_name
6
4
  @calling_service_name ||=
7
- request.headers[calling_service_header_name].presence || ""
8
- end
9
-
10
- private
11
-
12
- def calling_service_header_name
13
- configured = Stitches.configuration.calling_service_header
14
- configured.present? ? configured : DEFAULT_CALLING_SERVICE_HEADER
5
+ request.headers[Stitches.configuration.calling_service_header].presence || ""
15
6
  end
16
7
  end
17
8
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stitches
4
- VERSION = '5.1.0.RC4'
4
+ VERSION = '5.1.0.RC5'
5
5
  end
@@ -9,14 +9,14 @@ describe Stitches::CallingServiceMiddleware do
9
9
 
10
10
  describe "#call" do
11
11
  context "when the header is present and env var is not set" do
12
- before { env["HTTP_X_STITCHFIX_CALLING_SERVICE"] = "kingmob" }
12
+ before { env["HTTP_X_STITCHFIX_CALLING_SERVICE"] = "my-app" }
13
13
 
14
14
  it "populates the env var with a CallingServiceClient" do
15
15
  _status, result_env, _body = middleware.call(env)
16
16
  client = result_env[client_key]
17
17
 
18
18
  expect(client).to be_a(Stitches::CallingServiceClient)
19
- expect(client.name).to eq("kingmob")
19
+ expect(client.name).to eq("my-app")
20
20
  expect(client.id).to be_nil
21
21
  expect(client.key).to be_nil
22
22
  end
@@ -13,10 +13,10 @@ describe Stitches::CallingServiceName do
13
13
 
14
14
  describe "#calling_service_name" do
15
15
  context "when the header is present" do
16
- let(:headers) { {"X-StitchFix-Calling-Service" => "kingmob"} }
16
+ let(:headers) { {"X-StitchFix-Calling-Service" => "my-app"} }
17
17
 
18
18
  it "returns the header value" do
19
- expect(fake_controller.calling_service_name).to eq("kingmob")
19
+ expect(fake_controller.calling_service_name).to eq("my-app")
20
20
  end
21
21
  end
22
22
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stitches
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0.RC4
4
+ version: 5.1.0.RC5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stitch Fix Engineering