stytch 10.24.0 → 10.26.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.
data/lib/stytch/client.rb CHANGED
@@ -1,8 +1,15 @@
1
+ # !!!
2
+ # WARNING: This file is autogenerated
3
+ # Only modify code within MANUAL() sections
4
+ # or your changes may be overwritten later!
5
+ # !!!
6
+
1
7
  # frozen_string_literal: true
2
8
 
3
9
  require_relative 'connected_apps'
4
10
  require_relative 'crypto_wallets'
5
11
  require_relative 'fraud'
12
+ require_relative 'idp'
6
13
  require_relative 'impersonation'
7
14
  require_relative 'm2m'
8
15
  require_relative 'magic_links'
@@ -10,6 +17,8 @@ require_relative 'oauth'
10
17
  require_relative 'otps'
11
18
  require_relative 'passwords'
12
19
  require_relative 'project'
20
+ require_relative 'rbac'
21
+ require_relative 'rbac_local'
13
22
  require_relative 'sessions'
14
23
  require_relative 'totps'
15
24
  require_relative 'users'
@@ -19,7 +28,7 @@ module Stytch
19
28
  class Client
20
29
  ENVIRONMENTS = %i[live test].freeze
21
30
 
22
- attr_reader :connected_app, :crypto_wallets, :fraud, :impersonation, :m2m, :magic_links, :oauth, :otps, :passwords, :project, :sessions, :totps, :users, :webauthn
31
+ attr_reader :connected_app, :crypto_wallets, :fraud, :impersonation, :m2m, :magic_links, :oauth, :otps, :passwords, :project, :rbac, :sessions, :totps, :users, :webauthn, :idp
23
32
 
24
33
  def initialize(project_id:, secret:, env: nil, fraud_env: nil, &block)
25
34
  @api_host = api_host(env, project_id)
@@ -30,6 +39,10 @@ module Stytch
30
39
 
31
40
  create_connection(&block)
32
41
 
42
+ rbac = Stytch::RBAC.new(@connection)
43
+ @policy_cache = Stytch::PolicyCache.new(rbac_client: rbac)
44
+ @idp = Stytch::IDP.new(@connection, @project_id, @policy_cache)
45
+
33
46
  @connected_app = Stytch::ConnectedApp.new(@connection)
34
47
  @crypto_wallets = Stytch::CryptoWallets.new(@connection)
35
48
  @fraud = Stytch::Fraud.new(@fraud_connection)
@@ -40,7 +53,8 @@ module Stytch
40
53
  @otps = Stytch::OTPs.new(@connection)
41
54
  @passwords = Stytch::Passwords.new(@connection)
42
55
  @project = Stytch::Project.new(@connection)
43
- @sessions = Stytch::Sessions.new(@connection, @project_id)
56
+ @rbac = Stytch::RBAC.new(@connection)
57
+ @sessions = Stytch::Sessions.new(@connection, @project_id, @policy_cache)
44
58
  @totps = Stytch::TOTPs.new(@connection)
45
59
  @users = Stytch::Users.new(@connection)
46
60
  @webauthn = Stytch::WebAuthn.new(@connection)
data/lib/stytch/fraud.rb CHANGED
@@ -28,14 +28,17 @@ module Stytch
28
28
  @connection = connection
29
29
  end
30
30
 
31
- # Lookup the associated fingerprint for the `telemetry_id` returned from the `GetTelemetryID()` function. Learn more about the different fingerprint types and verdicts in our [DFP guide](https://stytch.com/docs/fraud/guides/device-fingerprinting/overview).
31
+ # Lookup the associated fingerprint for the `telemetry_id` returned from the `GetTelemetryID()` function.
32
+ # Learn more about the different fingerprint types and verdicts in our [DFP guide](https://stytch.com/docs/fraud/guides/device-fingerprinting/overview).
32
33
  #
33
- # Make a decision based on the returned `verdict`:
34
+ # You can make a decision based on the recommended `verdict` in the response:
34
35
  # * `ALLOW` - This is a known valid device grouping or device profile that is part of the default `ALLOW` listed set of known devices by Stytch. This grouping is made up of verified device profiles that match the characteristics of known/authentic traffic origins.
35
36
  # * `BLOCK` - This is a known bad or malicious device profile that is undesirable and should be blocked from completing the privileged action in question.
36
37
  # * `CHALLENGE` - This is an unknown or potentially malicious device that should be put through increased friction such as 2FA or other forms of extended user verification before allowing the privileged action to proceed.
37
38
  #
38
- # If the `telemetry_id` is not found, we will return a 404 `telemetry_id_not_found` [error](https://stytch.com/docs/fraud/api/errors/404#telemetry_id_not_found). We recommend treating 404 errors as a `BLOCK`, since it could be a sign of an attacker trying to bypass DFP protections by generating fake telemetry IDs.
39
+ # If the `telemetry_id` is expired or not found, this endpoint returns a 404 `telemetry_id_not_found` [error](https://stytch.com/docs/fraud/api/errors/404#telemetry_id_not_found).
40
+ # We recommend treating 404 errors as a `BLOCK`, since it could be a sign of an attacker trying to bypass DFP protections.
41
+ # See [Attacker-controlled telemetry IDs](https://stytch.com/docs/fraud/guides/device-fingerprinting/integration-steps/test-your-integration#attacker-controlled-telemetry-ids) for more information.
39
42
  #
40
43
  # == Parameters:
41
44
  # telemetry_id::
data/lib/stytch/idp.rb ADDED
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+ require 'json/jwt'
5
+ require_relative 'errors'
6
+ require_relative 'request_helper'
7
+ require_relative 'rbac_local'
8
+
9
+ module Stytch
10
+ class IDP
11
+ include Stytch::RequestHelper
12
+
13
+ def initialize(connection, project_id, policy_cache)
14
+ @connection = connection
15
+ @project_id = project_id
16
+ @policy_cache = policy_cache
17
+ @non_custom_claim_keys = %w[
18
+ aud
19
+ exp
20
+ iat
21
+ iss
22
+ jti
23
+ nbf
24
+ sub
25
+ active
26
+ client_id
27
+ request_id
28
+ scope
29
+ status_code
30
+ token_type
31
+ ]
32
+ end
33
+
34
+ # Introspects a token JWT from an authorization code response.
35
+ # Access tokens are JWTs signed with the project's JWKs. Refresh tokens are opaque tokens.
36
+ # Access tokens contain a standard set of claims as well as any custom claims generated from templates.
37
+ #
38
+ # == Parameters:
39
+ # token::
40
+ # The access token (or refresh token) to introspect.
41
+ # The type of this field is +String+.
42
+ # client_id::
43
+ # The ID of the client.
44
+ # The type of this field is +String+.
45
+ # client_secret::
46
+ # The secret of the client.
47
+ # The type of this field is nilable +String+.
48
+ # token_type_hint::
49
+ # A hint on what the token contains. Valid fields are 'access_token' and 'refresh_token'.
50
+ # The type of this field is +String+.
51
+ # authorization_check::
52
+ # Optional authorization check object.
53
+ # The type of this field is nilable +Hash+.
54
+ #
55
+ # == Returns:
56
+ # An object with the following fields:
57
+ # subject::
58
+ # The subject of the token.
59
+ # The type of this field is +String+.
60
+ # scope::
61
+ # The scope of the token.
62
+ # The type of this field is +String+.
63
+ # audience::
64
+ # The audience of the token.
65
+ # The type of this field is +String+.
66
+ # expires_at::
67
+ # The expiration time of the token.
68
+ # The type of this field is +Integer+.
69
+ # issued_at::
70
+ # The issued at time of the token.
71
+ # The type of this field is +Integer+.
72
+ # issuer::
73
+ # The issuer of the token.
74
+ # The type of this field is +String+.
75
+ # not_before::
76
+ # The not before time of the token.
77
+ # The type of this field is +Integer+.
78
+ # token_type::
79
+ # The type of the token.
80
+ # The type of this field is +String+.
81
+ # custom_claims::
82
+ # Custom claims in the token.
83
+ # The type of this field is +Hash+.
84
+ def introspect_token_network(
85
+ token:,
86
+ client_id:,
87
+ client_secret: nil,
88
+ token_type_hint: 'access_token',
89
+ authorization_check: nil
90
+ )
91
+ headers = {}
92
+ data = {
93
+ 'token' => token,
94
+ 'client_id' => client_id,
95
+ 'token_type_hint' => token_type_hint
96
+ }
97
+ data['client_secret'] = client_secret unless client_secret.nil?
98
+
99
+ url = @connection.url_prefix + '/v1/oauth2/introspect'
100
+ res = post_request(url, data, headers)
101
+
102
+ jwt_response = res
103
+ return nil unless jwt_response['active']
104
+
105
+ custom_claims = res.reject { |k, _| @non_custom_claim_keys.include?(k) }
106
+ scope = jwt_response['scope']
107
+
108
+ if authorization_check
109
+ @policy_cache.perform_scope_authorization_check(
110
+ token_scopes: scope.split,
111
+ authorization_check: authorization_check
112
+ )
113
+ end
114
+
115
+ {
116
+ 'subject' => jwt_response['sub'],
117
+ 'scope' => jwt_response['scope'],
118
+ 'audience' => jwt_response['aud'],
119
+ 'expires_at' => jwt_response['exp'],
120
+ 'issued_at' => jwt_response['iat'],
121
+ 'issuer' => jwt_response['iss'],
122
+ 'not_before' => jwt_response['nbf'],
123
+ 'token_type' => jwt_response['token_type'],
124
+ 'custom_claims' => custom_claims
125
+ }
126
+ end
127
+
128
+ # Introspects a token JWT from an authorization code response.
129
+ # Access tokens are JWTs signed with the project's JWKs. Refresh tokens are opaque tokens.
130
+ # Access tokens contain a standard set of claims as well as any custom claims generated from templates.
131
+ #
132
+ # == Parameters:
133
+ # access_token::
134
+ # The access token (or refresh token) to introspect.
135
+ # The type of this field is +String+.
136
+ # authorization_check::
137
+ # Optional authorization check object.
138
+ # The type of this field is nilable +Hash+.
139
+ #
140
+ # == Returns:
141
+ # An object with the following fields:
142
+ # subject::
143
+ # The subject of the token.
144
+ # The type of this field is +String+.
145
+ # scope::
146
+ # The scope of the token.
147
+ # The type of this field is +String+.
148
+ # audience::
149
+ # The audience of the token.
150
+ # The type of this field is +String+.
151
+ # expires_at::
152
+ # The expiration time of the token.
153
+ # The type of this field is +Integer+.
154
+ # issued_at::
155
+ # The issued at time of the token.
156
+ # The type of this field is +Integer+.
157
+ # issuer::
158
+ # The issuer of the token.
159
+ # The type of this field is +String+.
160
+ # not_before::
161
+ # The not before time of the token.
162
+ # The type of this field is +Integer+.
163
+ # token_type::
164
+ # The type of the token.
165
+ # The type of this field is +String+.
166
+ # custom_claims::
167
+ # Custom claims in the token.
168
+ # The type of this field is +Hash+.
169
+ def introspect_access_token_local(
170
+ access_token:,
171
+ authorization_check: nil
172
+ )
173
+ scope_claim = 'scope'
174
+
175
+ # Create a JWKS loader similar to other classes in the codebase
176
+ @cache_last_update = 0
177
+ jwks_loader = lambda do |options|
178
+ @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - 300
179
+ if @cached_keys.nil?
180
+ @cached_keys = get_jwks(project_id: @project_id)
181
+ @cache_last_update = Time.now.to_i
182
+ end
183
+ @cached_keys
184
+ end
185
+
186
+ begin
187
+ decoded_jwt = JWT.decode(
188
+ access_token,
189
+ nil,
190
+ true,
191
+ {
192
+ algorithms: ['RS256'],
193
+ jwks: jwks_loader,
194
+ iss: ["stytch.com/#{@project_id}", @connection.url_prefix],
195
+ aud: @project_id
196
+ }
197
+ )[0]
198
+
199
+ generic_claims = decoded_jwt
200
+ custom_claims = generic_claims.reject { |k, _| @non_custom_claim_keys.include?(k) }
201
+ scope = generic_claims[scope_claim]
202
+
203
+ if authorization_check
204
+ @policy_cache.perform_scope_authorization_check(
205
+ token_scopes: scope.split,
206
+ authorization_check: authorization_check
207
+ )
208
+ end
209
+
210
+ {
211
+ 'subject' => generic_claims['sub'],
212
+ 'scope' => generic_claims[scope_claim],
213
+ 'audience' => generic_claims['aud'],
214
+ 'expires_at' => generic_claims['exp'],
215
+ 'issued_at' => generic_claims['iat'],
216
+ 'issuer' => generic_claims['iss'],
217
+ 'not_before' => generic_claims['nbf'],
218
+ 'token_type' => 'access_token',
219
+ 'custom_claims' => custom_claims
220
+ }
221
+ rescue JWT::InvalidIssuerError
222
+ raise Stytch::JWTInvalidIssuerError
223
+ rescue JWT::InvalidAudError
224
+ raise Stytch::JWTInvalidAudienceError
225
+ rescue JWT::ExpiredSignature
226
+ raise Stytch::JWTExpiredSignatureError
227
+ rescue JWT::IncorrectAlgorithm
228
+ raise Stytch::JWTIncorrectAlgorithmError
229
+ rescue JWT::DecodeError
230
+ nil
231
+ end
232
+ end
233
+
234
+ # Gets the JWKS for the project.
235
+ #
236
+ # == Parameters:
237
+ # project_id::
238
+ # The ID of the project.
239
+ # The type of this field is +String+.
240
+ #
241
+ # == Returns:
242
+ # The JWKS for the project.
243
+ # The type of this field is +Hash+.
244
+ def get_jwks(project_id:)
245
+ headers = {}
246
+ query_params = {}
247
+ request = request_with_query_params("/v1/sessions/jwks/#{project_id}", query_params)
248
+ get_request(request, headers)
249
+ end
250
+ end
251
+ end
@@ -23,7 +23,7 @@ module Stytch
23
23
  #
24
24
  # == Parameters:
25
25
  # impersonation_token::
26
- # The User Impersonation token to authenticate.
26
+ # The User Impersonation token to authenticate. Expires in 5 minutes by default.
27
27
  # The type of this field is +String+.
28
28
  #
29
29
  # == Returns:
data/lib/stytch/otps.rb CHANGED
@@ -130,7 +130,7 @@ module Stytch
130
130
  # ### Cost to send SMS OTP
131
131
  # Before configuring SMS or WhatsApp OTPs, please review how Stytch [bills the costs of international OTPs](https://stytch.com/pricing) and understand how to protect your app against [toll fraud](https://stytch.com/docs/guides/passcodes/toll-fraud/overview).
132
132
  #
133
- # __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Enable%20international%20SMS).
133
+ # __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please add those countries to your Project's allowlist via the [Dashboard](https://stytch.com/dashboard/country-code-allowlists) or [Programmatic Workspace Actions](https://stytch.com/docs/workspace-management/pwa/set-allowed-country-codes), and [add credit card details](https://stytch.com/dashboard/settings/billing) to your account.
134
134
  #
135
135
  # Even when international SMS is enabled, we do not support sending SMS to countries on our [Unsupported countries list](https://stytch.com/docs/guides/passcodes/unsupported-countries).
136
136
  #
@@ -212,7 +212,7 @@ module Stytch
212
212
  # ### Cost to send SMS OTP
213
213
  # Before configuring SMS or WhatsApp OTPs, please review how Stytch [bills the costs of international OTPs](https://stytch.com/pricing) and understand how to protect your app against [toll fraud](https://stytch.com/docs/guides/passcodes/toll-fraud/overview).
214
214
  #
215
- # __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Enable%20international%20SMS).
215
+ # __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please add those countries to your Project's allowlist via the [Dashboard](https://stytch.com/dashboard/country-code-allowlists) or [Programmatic Workspace Actions](https://stytch.com/docs/workspace-management/pwa/set-allowed-country-codes), and [add credit card details](https://stytch.com/dashboard/settings/billing) to your account.
216
216
  #
217
217
  # Even when international SMS is enabled, we do not support sending SMS to countries on our [Unsupported countries list](https://stytch.com/docs/guides/passcodes/unsupported-countries).
218
218
  #
@@ -408,7 +408,7 @@ module Stytch
408
408
  # login_redirect_url::
409
409
  # The URL Stytch redirects to after the OAuth flow is completed for a user that already exists. This URL should be a route in your application which will run `oauth.authenticate` (see below) and finish the login.
410
410
  #
411
- # The URL must be configured as a Login URL in the [Redirect URL page](https://stytch.com/docs/dashboard/redirect-urls). If the field is not specified, the default Login URL will be used.
411
+ # The URL must be configured as a Login URL in the [Redirect URL page](https://stytch.com/dashboard/redirect-urls). If the field is not specified, the default Login URL will be used.
412
412
  # The type of this field is nilable +String+.
413
413
  # locale::
414
414
  # Used to determine which language to use when sending the user this delivery method. Parameter is a [IETF BCP 47 language tag](https://www.w3.org/International/articles/language-tags/), e.g. `"en"`.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # !!!
4
+ # WARNING: This file is autogenerated
5
+ # Only modify code within MANUAL() sections
6
+ # or your changes may be overwritten later!
7
+ # !!!
8
+
9
+ require_relative 'request_helper'
10
+
11
+ module Stytch
12
+ class RBAC
13
+ include Stytch::RequestHelper
14
+
15
+ def initialize(connection)
16
+ @connection = connection
17
+ end
18
+
19
+ # Get the active RBAC Policy for your current Stytch Project. An RBAC Policy is the canonical document that stores all defined Resources and Roles within your RBAC permissioning model.
20
+ #
21
+ # When using the backend SDKs, the RBAC Policy will be cached to allow for local evaluations, eliminating the need for an extra request to Stytch.
22
+ # The policy will be refreshed if an authorization check is requested and the RBAC policy was last updated more than 5 minutes ago.
23
+ #
24
+ # Resources and Roles can be created and managed within the [RBAC page](https://stytch.com/dashboard/rbac) in the Dashboard.
25
+ # Additionally, [Role assignment](https://stytch.com/docs/guides/rbac/role-assignment) can be programmatically managed through certain Stytch API endpoints.
26
+ #
27
+ # Check out the [RBAC overview](https://stytch.com/docs/guides/rbac/overview) to learn more about Stytch's RBAC permissioning model.
28
+ #
29
+ # == Parameters:
30
+ #
31
+ # == Returns:
32
+ # An object with the following fields:
33
+ # request_id::
34
+ # Globally unique UUID that is returned with every API call. This value is important to log for debugging purposes; we may ask for this value to help identify a specific API call when helping you debug an issue.
35
+ # The type of this field is +String+.
36
+ # status_code::
37
+ # The HTTP status code of the response. Stytch follows standard HTTP response status code patterns, e.g. 2XX values equate to success, 3XX values are redirects, 4XX are client errors, and 5XX are server errors.
38
+ # The type of this field is +Integer+.
39
+ # policy::
40
+ # The RBAC Policy document that contains all defined Roles and Resources – which are managed in the [Dashboard](https://stytch.com/dashboard/rbac). Read more about these entities and how they work in our [RBAC overview](https://stytch.com/docs/guides/rbac/overview).
41
+ # The type of this field is nilable +Policy+ (+object+).
42
+ def policy
43
+ headers = {}
44
+ query_params = {}
45
+ request = request_with_query_params('/v1/rbac/policy', query_params)
46
+ get_request(request, headers)
47
+ end
48
+ end
49
+ end
@@ -3,7 +3,7 @@
3
3
  require_relative 'errors'
4
4
  require_relative 'request_helper'
5
5
 
6
- module StytchB2B
6
+ module Stytch
7
7
  class PolicyCache
8
8
  def initialize(rbac_client:)
9
9
  @rbac_client = rbac_client
@@ -31,8 +31,7 @@ module StytchB2B
31
31
  subject_org_id:,
32
32
  authorization_check:
33
33
  )
34
- request_org_id = authorization_check['organization_id']
35
- raise Stytch::TenancyError.new(subject_org_id, request_org_id) if request_org_id != subject_org_id
34
+ raise Stytch::TenancyError.new(subject_org_id, authorization_check['organization_id']) if subject_org_id != authorization_check['organization_id']
36
35
 
37
36
  policy = get_policy
38
37
 
@@ -54,5 +53,69 @@ module StytchB2B
54
53
  # If we get here, we didn't find a matching permission
55
54
  raise Stytch::PermissionError, authorization_check
56
55
  end
56
+
57
+ # Performs an authorization check against the project's policy and a set of roles. If the
58
+ # check succeeds, this method will return. If the check fails, a PermissionError
59
+ # will be raised. This is used for role based authorization.
60
+ def perform_consumer_authorization_check(
61
+ subject_roles:,
62
+ authorization_check:
63
+ )
64
+ policy = get_policy
65
+
66
+ # For consumer authorization, we check roles without tenancy validation
67
+ for role in policy['roles']
68
+ next unless subject_roles.include?(role['role_id'])
69
+
70
+ for permission in role['permissions']
71
+ actions = permission['actions']
72
+ resource = permission['resource_id']
73
+ has_matching_action = actions.include?('*') || actions.include?(authorization_check['action'])
74
+ has_matching_resource = resource == authorization_check['resource_id']
75
+ if has_matching_action && has_matching_resource
76
+ return # Permission granted
77
+ end
78
+ end
79
+ end
80
+
81
+ # If we get here, we didn't find a matching permission
82
+ raise Stytch::PermissionError, authorization_check
83
+ end
84
+
85
+ # Performs an authorization check against the project's policy and a set of scopes. If the
86
+ # check succeeds, this method will return. If the check fails, a PermissionError
87
+ # will be raised. This is used for OAuth-style scope-based authorization.
88
+ # authorization_check is an object with keys 'action' and 'resource_id'
89
+ def perform_scope_authorization_check(
90
+ token_scopes:,
91
+ authorization_check:
92
+ )
93
+ policy = get_policy
94
+
95
+ # For scope-based authorization, we check if any of the token scopes match policy scopes
96
+ # and if those scopes grant permission for the requested action/resource
97
+ action = authorization_check['action']
98
+ resource_id = authorization_check['resource_id']
99
+
100
+ # Check if any of the token scopes grant permission for this action/resource
101
+ for scope_obj in policy['scopes']
102
+ scope_name = scope_obj['scope']
103
+ next unless token_scopes.include?(scope_name)
104
+
105
+ # Check if this scope grants permission for the requested action/resource
106
+ for permission in scope_obj['permissions']
107
+ actions = permission['actions']
108
+ resource = permission['resource_id']
109
+ has_matching_action = actions.include?('*') || actions.include?(action)
110
+ has_matching_resource = resource == resource_id
111
+ if has_matching_action && has_matching_resource
112
+ return # Permission granted
113
+ end
114
+ end
115
+ end
116
+
117
+ # If we get here, we didn't find a matching permission
118
+ raise Stytch::PermissionError, authorization_check
119
+ end
57
120
  end
58
121
  end
@@ -15,9 +15,10 @@ module Stytch
15
15
  class Sessions
16
16
  include Stytch::RequestHelper
17
17
 
18
- def initialize(connection, project_id)
18
+ def initialize(connection, project_id, policy_cache)
19
19
  @connection = connection
20
20
 
21
+ @policy_cache = policy_cache
21
22
  @project_id = project_id
22
23
  @cache_last_update = 0
23
24
  @jwks_loader = lambda do |options|
@@ -81,6 +82,13 @@ module Stytch
81
82
  #
82
83
  # Custom claims made with reserved claims ("iss", "sub", "aud", "exp", "nbf", "iat", "jti") will be ignored. Total custom claims size cannot exceed four kilobytes.
83
84
  # The type of this field is nilable +object+.
85
+ # authorization_check::
86
+ # If an `authorization_check` object is passed in, this endpoint will also check if the User is
87
+ # authorized to perform the given action on the given Resource. A User is authorized if they are assigned a Role with adequate permissions.
88
+ #
89
+ # If the User is not authorized to perform the specified action on the specified Resource, a 403 error will be thrown.
90
+ # Otherwise, the response will contain a list of Roles that satisfied the authorization check.
91
+ # The type of this field is nilable +AuthorizationCheck+ (+object+).
84
92
  #
85
93
  # == Returns:
86
94
  # An object with the following fields:
@@ -105,11 +113,16 @@ module Stytch
105
113
  # status_code::
106
114
  # The HTTP status code of the response. Stytch follows standard HTTP response status code patterns, e.g. 2XX values equate to success, 3XX values are redirects, 4XX are client errors, and 5XX are server errors.
107
115
  # The type of this field is +Integer+.
116
+ # verdict::
117
+ # If an `authorization_check` is provided in the request and the check succeeds, this field will return
118
+ # information about why the User was granted permission.
119
+ # The type of this field is nilable +AuthorizationVerdict+ (+object+).
108
120
  def authenticate(
109
121
  session_token: nil,
110
122
  session_duration_minutes: nil,
111
123
  session_jwt: nil,
112
- session_custom_claims: nil
124
+ session_custom_claims: nil,
125
+ authorization_check: nil
113
126
  )
114
127
  headers = {}
115
128
  request = {}
@@ -117,6 +130,7 @@ module Stytch
117
130
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
118
131
  request[:session_jwt] = session_jwt unless session_jwt.nil?
119
132
  request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
133
+ request[:authorization_check] = authorization_check unless authorization_check.nil?
120
134
 
121
135
  post_request('/v1/sessions/authenticate', request, headers)
122
136
  end
@@ -156,7 +170,7 @@ module Stytch
156
170
  post_request('/v1/sessions/revoke', request, headers)
157
171
  end
158
172
 
159
- # Migrate a session from an external OIDC compliant endpoint. Stytch will call the external UserInfo endpoint defined in your Stytch Project settings in the [Dashboard](https://stytch.com/docs/dashboard), and then perform a lookup using the `session_token`. If the response contains a valid email address, Stytch will attempt to match that email address with an existing User and create a Stytch Session. You will need to create the user before using this endpoint.
173
+ # Migrate a session from an external OIDC compliant endpoint. Stytch will call the external UserInfo endpoint defined in your Stytch Project settings in the [Dashboard](https://stytch.com/dashboard), and then perform a lookup using the `session_token`. If the response contains a valid email address, Stytch will attempt to match that email address with an existing User and create a Stytch Session. You will need to create the user before using this endpoint.
160
174
  #
161
175
  # == Parameters:
162
176
  # session_token::
@@ -326,7 +340,7 @@ module Stytch
326
340
  get_request(request, headers)
327
341
  end
328
342
 
329
- # Exchange an auth token issued by a trusted identity provider for a Stytch session. You must first register a Trusted Auth Token profile in the Stytch dashboard [here](https://stytch.com/docs/dashboard/trusted-auth-tokens). If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.
343
+ # Exchange an auth token issued by a trusted identity provider for a Stytch session. You must first register a Trusted Auth Token profile in the Stytch dashboard [here](https://stytch.com/dashboard/trusted-auth-tokens). If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.
330
344
  #
331
345
  # == Parameters:
332
346
  # profile_id::
@@ -421,7 +435,8 @@ module Stytch
421
435
  max_token_age_seconds: nil,
422
436
  session_duration_minutes: nil,
423
437
  session_custom_claims: nil,
424
- clock_tolerance_seconds: nil
438
+ clock_tolerance_seconds: nil,
439
+ authorization_check: nil
425
440
  )
426
441
  max_token_age_seconds = 300 if max_token_age_seconds.nil?
427
442
  clock_tolerance_seconds = 0 if clock_tolerance_seconds.nil?
@@ -430,28 +445,32 @@ module Stytch
430
445
  return authenticate(
431
446
  session_jwt: session_jwt,
432
447
  session_duration_minutes: session_duration_minutes,
433
- session_custom_claims: session_custom_claims
448
+ session_custom_claims: session_custom_claims,
449
+ authorization_check: authorization_check
434
450
  )
435
451
  end
436
452
 
437
453
  session = authenticate_jwt_local(
438
454
  session_jwt,
439
455
  max_token_age_seconds: max_token_age_seconds,
440
- clock_tolerance_seconds: clock_tolerance_seconds
456
+ clock_tolerance_seconds: clock_tolerance_seconds,
457
+ authorization_check: authorization_check
441
458
  )
442
459
  return session unless session.nil?
443
460
 
444
461
  authenticate(
445
462
  session_jwt: session_jwt,
446
463
  session_duration_minutes: session_duration_minutes,
447
- session_custom_claims: session_custom_claims
464
+ session_custom_claims: session_custom_claims,
465
+ authorization_check: authorization_check
448
466
  )
449
467
  rescue StandardError
450
468
  # JWT could not be verified locally. Check with the Stytch API.
451
469
  authenticate(
452
470
  session_jwt: session_jwt,
453
471
  session_duration_minutes: session_duration_minutes,
454
- session_custom_claims: session_custom_claims
472
+ session_custom_claims: session_custom_claims,
473
+ authorization_check: authorization_check
455
474
  )
456
475
  end
457
476
 
@@ -461,7 +480,7 @@ module Stytch
461
480
  # This method never authenticates a JWT directly with the API
462
481
  # If max_token_age_seconds is not supplied 300 seconds will be used as the default.
463
482
  # If clock_tolerance_seconds is not supplied 0 seconds will be used as the default.
464
- def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil, clock_tolerance_seconds: nil)
483
+ def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil, clock_tolerance_seconds: nil, authorization_check: nil)
465
484
  max_token_age_seconds = 300 if max_token_age_seconds.nil?
466
485
  clock_tolerance_seconds = 0 if clock_tolerance_seconds.nil?
467
486
 
@@ -488,6 +507,14 @@ module Stytch
488
507
  raise JWTIncorrectAlgorithmError
489
508
  end
490
509
 
510
+ # Do the auth check - intentionally don't rescue errors from here
511
+ if authorization_check
512
+ @policy_cache.perform_consumer_authorization_check(
513
+ subject_roles: session['roles'],
514
+ authorization_check: authorization_check
515
+ )
516
+ end
517
+
491
518
  session
492
519
  end
493
520
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stytch
4
- VERSION = '10.24.0'
4
+ VERSION = '10.26.0'
5
5
  end
data/lib/stytch.rb CHANGED
@@ -4,6 +4,7 @@ require 'faraday'
4
4
 
5
5
  require_relative 'stytch/b2b_client'
6
6
  require_relative 'stytch/client'
7
+ require_relative 'stytch/idp'
7
8
  require_relative 'stytch/method_options'
8
9
  require_relative 'stytch/middleware'
9
10
  require_relative 'stytch/version'