standard_id 0.52.0 → 0.53.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.
@@ -55,6 +55,105 @@ module StandardId
55
55
  def bearer_auth_header(token)
56
56
  { "Authorization" => "Bearer #{token}" }
57
57
  end
58
+
59
+ # Bearer headers for an account, in one call: build_jwt + bearer_auth_header.
60
+ #
61
+ # get "/api/orders", headers: bearer_headers_for(account, scope: "orders.read")
62
+ #
63
+ # @param account [Object] the account (its id becomes the sub claim)
64
+ # @param jwt_options [Hash] any other build_jwt keyword (scope:, client_id:, extra:, ...)
65
+ # @return [Hash] header hash
66
+ #
67
+ def bearer_headers_for(account, **jwt_options)
68
+ bearer_auth_header(build_jwt(account: account, **jwt_options))
69
+ end
70
+
71
+ # Sign an account in for the following requests in a request spec,
72
+ # without driving a login form or stubbing controllers.
73
+ #
74
+ # Creates a real BrowserSession and writes the same encrypted
75
+ # `session_token` cookie that Web::SessionManager writes on a real
76
+ # sign-in, so `current_account`, `require_browser_session!` and the
77
+ # session's expiry and revocation all behave exactly as in production.
78
+ # With `scope:`, it also writes the Rails session cookie carrying
79
+ # `standard_id_scopes` (what `current_scope_names` reads), which needs
80
+ # the cookie session store; any other store raises ArgumentError.
81
+ #
82
+ # Nothing is added to the app: the cookies are built with the app's own
83
+ # encrypted cookie jar and secret_key_base, so there is no test-only route
84
+ # to forget to remove. Like a real sign-in (which resets the session), it
85
+ # always replaces the Rails session cookie: an earlier request's session
86
+ # data, and an earlier sign_in_as's scopes, never carry over to this
87
+ # account.
88
+ #
89
+ # sign_in_as(account)
90
+ # sign_in_as(account, scope: :platform)
91
+ # get "/dashboard"
92
+ #
93
+ # @param account [Object] the account to sign in
94
+ # @param scope [Symbol, String, Array, nil] scope name(s) to grant
95
+ # @param user_agent [String] stored on the BrowserSession
96
+ # @return [StandardId::BrowserSession] the session that was created
97
+ #
98
+ def sign_in_as(account, scope: nil, user_agent: "RSpec")
99
+ ensure_cookie_session_store! if scope
100
+ browser_session = create_browser_session(account, user_agent: user_agent)
101
+ jar = standard_id_cookie_jar
102
+
103
+ jar.encrypted[:session_token] = {
104
+ value: browser_session.token,
105
+ expires: browser_session.expires_at,
106
+ httponly: true
107
+ }
108
+ cookies[:session_token] = jar[:session_token]
109
+
110
+ if scope
111
+ write_standard_id_scopes(jar, scope)
112
+ elsif (key = rails_session_key)
113
+ cookies.delete(key)
114
+ end
115
+ browser_session
116
+ end
117
+
118
+ private
119
+
120
+ # The host app's cookie jar, outside any request: same secret_key_base,
121
+ # salts, serializer and rotations as a real request, so the cookies it
122
+ # writes read back exactly as Web::SessionManager's own.
123
+ def standard_id_cookie_jar
124
+ ActionDispatch::Request.new(Rails.application.env_config.deep_dup).cookie_jar
125
+ end
126
+
127
+ # Checked before anything is created, so a refused call leaves no session.
128
+ def ensure_cookie_session_store!
129
+ store = Rails.application.config.session_store
130
+ unless store.is_a?(Class) && store <= ActionDispatch::Session::CookieStore
131
+ raise ArgumentError,
132
+ "sign_in_as(scope:) writes the Rails session cookie, so it needs the cookie " \
133
+ "session store; this app uses #{store.inspect}. " \
134
+ "Sign in through the app's own scoped login request instead."
135
+ end
136
+ return if rails_session_key
137
+
138
+ raise ArgumentError,
139
+ "sign_in_as(scope:) needs the session cookie name (config.session_options[:key]), and it is not set"
140
+ end
141
+
142
+ def rails_session_key
143
+ Rails.application.config.session_options[:key]&.to_s
144
+ end
145
+
146
+ def write_standard_id_scopes(jar, scope)
147
+ key = rails_session_key
148
+ jar.encrypted[key] = {
149
+ value: {
150
+ "session_id" => SecureRandom.hex(16),
151
+ "standard_id_scopes" => Array(scope).map(&:to_s)
152
+ },
153
+ httponly: true
154
+ }
155
+ cookies[key] = jar[key]
156
+ end
58
157
  end
59
158
  end
60
159
  end
@@ -1,3 +1,3 @@
1
1
  module StandardId
2
- VERSION = "0.52.0"
2
+ VERSION = "0.53.0"
3
3
  end
data/lib/standard_id.rb CHANGED
@@ -8,6 +8,7 @@ require "standard_id/api_engine"
8
8
  require "standard_id/config_schema"
9
9
  require "standard_id/config/schema"
10
10
  require "standard_id/config/presets"
11
+ require "standard_id/app_environment"
11
12
  require "standard_id/scope_config"
12
13
  require "standard_id/association_strict_loading"
13
14
  require "standard_id/migration_check"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.52.0
4
+ version: 0.53.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim
@@ -245,6 +245,7 @@ files:
245
245
  - db/migrate/20261020120000_add_registration_access_token_digest_to_standard_id_client_applications.rb
246
246
  - lib/generators/standard_id/install/install_generator.rb
247
247
  - lib/generators/standard_id/install/templates/standard_id.rb
248
+ - lib/generators/standard_id/install/templates/standard_id_reference.rb
248
249
  - lib/standard_id.rb
249
250
  - lib/standard_id/account_cleanup.rb
250
251
  - lib/standard_id/account_locking.rb
@@ -253,6 +254,7 @@ files:
253
254
  - lib/standard_id/api/session_manager.rb
254
255
  - lib/standard_id/api/token_manager.rb
255
256
  - lib/standard_id/api_engine.rb
257
+ - lib/standard_id/app_environment.rb
256
258
  - lib/standard_id/association_strict_loading.rb
257
259
  - lib/standard_id/auth_lineage.rb
258
260
  - lib/standard_id/authorization_bypass.rb