stytch 10.28.0 → 10.29.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: 84042207ac59233533cc823e147225b53dfb242577ada92f7c371d91f0067346
4
- data.tar.gz: 43abe43af046750571190adbb0aba08f2126c5781812385c7bf4cb88ee41871c
3
+ metadata.gz: 9837d4362966d389505ea7b235379cb635e2dc5eda22537dac31e8ec0f6e1bcd
4
+ data.tar.gz: 72ba0b2c7af66364d677ed6ae70ed3278a0375e40e6a4f00dbbe912d31407cdf
5
5
  SHA512:
6
- metadata.gz: 579d066dd8ebbda2724b7ff7824cd92686bdfecdabf10df8cf562f29a6025f24e814a746832838868c173800aac0399fac882cb52a5e281d5e776b99f4057796
7
- data.tar.gz: 6dc2c0f7ae5a048d4bba0bebcf7a002f94afc7dee8d82f844147313920212814256b9aa1ad21d4cf6dbcd6396215e5213285494475e31b17aa4f47e01e75be65
6
+ metadata.gz: 3e359e41dc088a10f7174b2f72df1c3f552a82e92df50b5dd5c7b7e6cb7f6e053e3fc6a3b42f518ca66bd663d930957ece13c53b3a3ff4ddbaef788edad5ce5b
7
+ data.tar.gz: ea6fb4ed455954c907ed1d8d34cdb09057de8485037b3474b16ecdab6edfc50241737805a879a22d15c6ecbe9d63ad69532483e88605b1b58d378d0d8f201ad2
@@ -30,7 +30,7 @@ module StytchB2B
30
30
  class Client
31
31
  ENVIRONMENTS = %i[live test].freeze
32
32
 
33
- attr_reader :connected_app, :discovery, :fraud, :impersonation, :m2m, :magic_links, :oauth, :otps, :organizations, :passwords, :project, :rbac, :recovery_codes, :scim, :sso, :sessions, :totps, :idp
33
+ attr_reader :connected_app, :discovery, :fraud, :idp, :impersonation, :m2m, :magic_links, :oauth, :otps, :organizations, :passwords, :project, :rbac, :recovery_codes, :scim, :sso, :sessions, :totps
34
34
 
35
35
  def initialize(project_id:, secret:, env: nil, fraud_env: nil, &block)
36
36
  @api_host = api_host(env, project_id)
@@ -43,11 +43,11 @@ module StytchB2B
43
43
 
44
44
  rbac = StytchB2B::RBAC.new(@connection)
45
45
  @policy_cache = Stytch::PolicyCache.new(rbac_client: rbac)
46
- @idp = StytchB2B::IDP.new(@connection, @project_id, @policy_cache)
47
46
 
48
47
  @connected_app = Stytch::ConnectedApp.new(@connection)
49
48
  @discovery = StytchB2B::Discovery.new(@connection)
50
49
  @fraud = Stytch::Fraud.new(@fraud_connection)
50
+ @idp = StytchB2B::IDP.new(@connection, @project_id, @policy_cache)
51
51
  @impersonation = StytchB2B::Impersonation.new(@connection)
52
52
  @m2m = Stytch::M2M.new(@connection, @project_id, @is_b2b_client)
53
53
  @magic_links = StytchB2B::MagicLinks.new(@connection)
@@ -1,37 +1,46 @@
1
1
  # frozen_string_literal: true
2
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
+
3
9
  require 'jwt'
4
10
  require 'json/jwt'
5
11
  require_relative 'errors'
6
12
  require_relative 'request_helper'
7
- require_relative 'rbac_local'
8
13
 
9
14
  module StytchB2B
10
15
  class IDP
11
16
  include Stytch::RequestHelper
17
+ attr_reader :oauth
12
18
 
13
19
  def initialize(connection, project_id, policy_cache)
14
20
  @connection = connection
15
- @project_id = project_id
21
+
22
+ @oauth = StytchB2B::IDP::OAuth.new(@connection)
16
23
  @policy_cache = policy_cache
17
- @non_custom_claim_keys = [
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
- 'https://stytch.com/organization'
32
- ]
24
+ @project_id = project_id
25
+ @cache_last_update = 0
26
+ @jwks_loader = lambda do |options|
27
+ @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - 300
28
+ @cached_keys ||= begin
29
+ @cache_last_update = Time.now.to_i
30
+ keys = []
31
+ get_jwks(project_id: @project_id)['keys'].each do |r|
32
+ keys << r
33
+ end
34
+ { keys: keys }
35
+ end
36
+ end
33
37
  end
34
38
 
39
+ # MANUAL(IDP::introspect_token_network)(SERVICE_METHOD)
40
+ # ADDIMPORT: require 'jwt'
41
+ # ADDIMPORT: require 'json/jwt'
42
+ # ADDIMPORT: require_relative 'errors'
43
+
35
44
  # Introspects a token JWT from an authorization code response.
36
45
  # Access tokens are JWTs signed with the project's JWKs. Refresh tokens are opaque tokens.
37
46
  # Access tokens contain a standard set of claims as well as any custom claims generated from templates.
@@ -105,7 +114,7 @@ module StytchB2B
105
114
 
106
115
  return nil unless jwt_response['active']
107
116
 
108
- custom_claims = jwt_response.reject { |k, _| @non_custom_claim_keys.include?(k) }
117
+ custom_claims = jwt_response.reject { |k, _| non_custom_claim_keys.include?(k) }
109
118
  organization_claim = jwt_response['https://stytch.com/organization']
110
119
  organization_id = organization_claim['organization_id']
111
120
  scope = jwt_response['scope']
@@ -183,17 +192,6 @@ module StytchB2B
183
192
  scope_claim = 'scope'
184
193
  organization_claim = 'https://stytch.com/organization'
185
194
 
186
- # Create a JWKS loader similar to other classes in the codebase
187
- @cache_last_update = 0
188
- jwks_loader = lambda do |options|
189
- @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - 300
190
- if @cached_keys.nil?
191
- @cached_keys = get_jwks(project_id: @project_id)
192
- @cache_last_update = Time.now.to_i
193
- end
194
- @cached_keys
195
- end
196
-
197
195
  begin
198
196
  decoded_jwt = JWT.decode(
199
197
  access_token,
@@ -201,14 +199,14 @@ module StytchB2B
201
199
  true,
202
200
  {
203
201
  algorithms: ['RS256'],
204
- jwks: jwks_loader,
202
+ jwks: @jwks_loader,
205
203
  iss: ["stytch.com/#{@project_id}", @connection.url_prefix],
206
204
  aud: @project_id
207
205
  }
208
206
  )[0]
209
207
 
210
208
  generic_claims = decoded_jwt
211
- custom_claims = generic_claims.reject { |k, _| @non_custom_claim_keys.include?(k) }
209
+ custom_claims = generic_claims.reject { |k, _| non_custom_claim_keys.include?(k) }
212
210
  organization_claim_data = generic_claims[organization_claim]
213
211
  organization_id = organization_claim_data['organization_id']
214
212
  scope = generic_claims[scope_claim]
@@ -246,6 +244,27 @@ module StytchB2B
246
244
  end
247
245
  end
248
246
 
247
+ private
248
+
249
+ def non_custom_claim_keys
250
+ [
251
+ 'aud',
252
+ 'exp',
253
+ 'iat',
254
+ 'iss',
255
+ 'jti',
256
+ 'nbf',
257
+ 'sub',
258
+ 'active',
259
+ 'client_id',
260
+ 'request_id',
261
+ 'scope',
262
+ 'status_code',
263
+ 'token_type',
264
+ 'https://stytch.com/organization'
265
+ ]
266
+ end
267
+
249
268
  # Gets the JWKS for the project.
250
269
  #
251
270
  # == Parameters:
@@ -262,5 +281,226 @@ module StytchB2B
262
281
  request = request_with_query_params("/v1/b2b/sessions/jwks/#{project_id}", query_params)
263
282
  get_request(request, headers)
264
283
  end
284
+
285
+ # ENDMANUAL(IDP::introspect_token_network)
286
+
287
+ class OAuth
288
+ include Stytch::RequestHelper
289
+
290
+ def initialize(connection)
291
+ @connection = connection
292
+ end
293
+
294
+ # Initiates a request for authorization of a Connected App to access a Member's account.
295
+ #
296
+ # Call this endpoint using the query parameters from an OAuth Authorization request.
297
+ # This endpoint validates various fields (`scope`, `client_id`, `redirect_uri`, `prompt`, etc...) are correct and returns
298
+ # relevant information for rendering an OAuth Consent Screen.
299
+ #
300
+ # This endpoint returns:
301
+ # - A public representation of the Connected App requesting authorization
302
+ # - Whether _explicit_ consent must be granted before proceeding with the authorization
303
+ # - A list of scopes the Member has the ability to grant the Connected App
304
+ #
305
+ # Use this response to prompt the Member for consent (if necessary) before calling the [Submit OAuth Authorization](https://stytch.com/docs/b2b/api/connected-apps-oauth-authorize) endpoint.
306
+ #
307
+ # Exactly one of the following must be provided to identify the Member granting authorization:
308
+ # - `organization_id` + `member_id`
309
+ # - `session_token`
310
+ # - `session_jwt`
311
+ #
312
+ # If a `session_token` or `session_jwt` is passed, the OAuth Authorization will be linked to the Member's session for tracking purposes.
313
+ # One of these fields must be used if the Connected App intends to complete the [Exchange Access Token](https://stytch.com/docs/b2b/api/connected-app-access-token-exchange) flow.
314
+ #
315
+ # == Parameters:
316
+ # client_id::
317
+ # The ID of the Connected App client.
318
+ # The type of this field is +String+.
319
+ # redirect_uri::
320
+ # The callback URI used to redirect the user after authentication. This is the same URI provided at the start of the OAuth flow. This field is required when using the `authorization_code` grant.
321
+ # The type of this field is +String+.
322
+ # response_type::
323
+ # The OAuth 2.0 response type. For authorization code flows this value is `code`.
324
+ # The type of this field is +String+.
325
+ # scopes::
326
+ # An array of scopes requested by the client.
327
+ # The type of this field is list of +String+.
328
+ # organization_id::
329
+ # Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. You may also use the organization_slug or organization_external_id here as a convenience.
330
+ # The type of this field is nilable +String+.
331
+ # member_id::
332
+ # Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. You may use an external_id here if one is set for the member.
333
+ # The type of this field is nilable +String+.
334
+ # session_token::
335
+ # A secret token for a given Stytch Session.
336
+ # The type of this field is nilable +String+.
337
+ # session_jwt::
338
+ # The JSON Web Token (JWT) for a given Stytch Session.
339
+ # The type of this field is nilable +String+.
340
+ # prompt::
341
+ # Space separated list that specifies how the Authorization Server should prompt the user for reauthentication and consent. Only `consent` is supported today.
342
+ # The type of this field is nilable +String+.
343
+ #
344
+ # == Returns:
345
+ # An object with the following fields:
346
+ # request_id::
347
+ # 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.
348
+ # The type of this field is +String+.
349
+ # member_id::
350
+ # Globally unique UUID that identifies a specific Member.
351
+ # The type of this field is +String+.
352
+ # member::
353
+ # The [Member object](https://stytch.com/docs/b2b/api/member-object)
354
+ # The type of this field is +Member+ (+object+).
355
+ # organization::
356
+ # The [Organization object](https://stytch.com/docs/b2b/api/organization-object).
357
+ # The type of this field is +Organization+ (+object+).
358
+ # client::
359
+ # (no documentation yet)
360
+ # The type of this field is +ConnectedAppPublic+ (+object+).
361
+ # consent_required::
362
+ # Whether the user must provide explicit consent for the authorization request.
363
+ # The type of this field is +Boolean+.
364
+ # scope_results::
365
+ # Details about each requested scope.
366
+ # The type of this field is list of +ScopeResult+ (+object+).
367
+ # status_code::
368
+ # (no documentation yet)
369
+ # The type of this field is +Integer+.
370
+ def authorize_start(
371
+ client_id:,
372
+ redirect_uri:,
373
+ response_type:,
374
+ scopes:,
375
+ organization_id: nil,
376
+ member_id: nil,
377
+ session_token: nil,
378
+ session_jwt: nil,
379
+ prompt: nil
380
+ )
381
+ headers = {}
382
+ request = {
383
+ client_id: client_id,
384
+ redirect_uri: redirect_uri,
385
+ response_type: response_type,
386
+ scopes: scopes
387
+ }
388
+ request[:organization_id] = organization_id unless organization_id.nil?
389
+ request[:member_id] = member_id unless member_id.nil?
390
+ request[:session_token] = session_token unless session_token.nil?
391
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
392
+ request[:prompt] = prompt unless prompt.nil?
393
+
394
+ post_request('/v1/b2b/idp/oauth/authorize/start', request, headers)
395
+ end
396
+
397
+ # Completes a request for authorization of a Connected App to access a Member's account.
398
+ #
399
+ # Call this endpoint using the query parameters from an OAuth Authorization request, after previously validating those parameters using the
400
+ # [Preflight Check](https://stytch.com/docs/b2b/api/connected-apps-oauth-authorize-start) API.
401
+ # Note that this endpoint takes in a few additional parameters the preflight check does not- `state`, `nonce`, and `code_challenge`.
402
+ #
403
+ # If the authorization was successful, the `redirect_uri` will contain a valid `authorization_code` embedded as a query parameter.
404
+ # If the authorization was unsuccessful, the `redirect_uri` will contain an OAuth2.1 `error_code`.
405
+ # In both cases, redirect the Member to the location for the response to be consumed by the Connected App.
406
+ #
407
+ # Exactly one of the following must be provided to identify the Member granting authorization:
408
+ # - `organization_id` + `member_id`
409
+ # - `session_token`
410
+ # - `session_jwt`
411
+ #
412
+ # If a `session_token` or `session_jwt` is passed, the OAuth Authorization will be linked to the Member's session for tracking purposes.
413
+ # One of these fields must be used if the Connected App intends to complete the [Exchange Access Token](https://stytch.com/docs/b2b/api/connected-app-access-token-exchange) flow.
414
+ #
415
+ # == Parameters:
416
+ # consent_granted::
417
+ # Indicates whether the user granted the requested scopes.
418
+ # The type of this field is +Boolean+.
419
+ # scopes::
420
+ # An array of scopes requested by the client.
421
+ # The type of this field is list of +String+.
422
+ # client_id::
423
+ # The ID of the Connected App client.
424
+ # The type of this field is +String+.
425
+ # redirect_uri::
426
+ # The callback URI used to redirect the user after authentication. This is the same URI provided at the start of the OAuth flow. This field is required when using the `authorization_code` grant.
427
+ # The type of this field is +String+.
428
+ # response_type::
429
+ # The OAuth 2.0 response type. For authorization code flows this value is `code`.
430
+ # The type of this field is +String+.
431
+ # organization_id::
432
+ # Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value. You may also use the organization_slug or organization_external_id here as a convenience.
433
+ # The type of this field is nilable +String+.
434
+ # member_id::
435
+ # Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value. You may use an external_id here if one is set for the member.
436
+ # The type of this field is nilable +String+.
437
+ # session_token::
438
+ # A secret token for a given Stytch Session.
439
+ # The type of this field is nilable +String+.
440
+ # session_jwt::
441
+ # The JSON Web Token (JWT) for a given Stytch Session.
442
+ # The type of this field is nilable +String+.
443
+ # prompt::
444
+ # Space separated list that specifies how the Authorization Server should prompt the user for reauthentication and consent. Only `consent` is supported today.
445
+ # The type of this field is nilable +String+.
446
+ # state::
447
+ # An opaque value used to maintain state between the request and callback.
448
+ # The type of this field is nilable +String+.
449
+ # nonce::
450
+ # A string used to associate a client session with an ID token to mitigate replay attacks.
451
+ # The type of this field is nilable +String+.
452
+ # code_challenge::
453
+ # A base64url encoded challenge derived from the code verifier for PKCE flows.
454
+ # The type of this field is nilable +String+.
455
+ #
456
+ # == Returns:
457
+ # An object with the following fields:
458
+ # request_id::
459
+ # 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.
460
+ # The type of this field is +String+.
461
+ # redirect_uri::
462
+ # The callback URI used to redirect the user after authentication. This is the same URI provided at the start of the OAuth flow. This field is required when using the `authorization_code` grant.
463
+ # The type of this field is +String+.
464
+ # status_code::
465
+ # (no documentation yet)
466
+ # The type of this field is +Integer+.
467
+ # authorization_code::
468
+ # A one-time use code that can be exchanged for tokens.
469
+ # The type of this field is nilable +String+.
470
+ def authorize(
471
+ consent_granted:,
472
+ scopes:,
473
+ client_id:,
474
+ redirect_uri:,
475
+ response_type:,
476
+ organization_id: nil,
477
+ member_id: nil,
478
+ session_token: nil,
479
+ session_jwt: nil,
480
+ prompt: nil,
481
+ state: nil,
482
+ nonce: nil,
483
+ code_challenge: nil
484
+ )
485
+ headers = {}
486
+ request = {
487
+ consent_granted: consent_granted,
488
+ scopes: scopes,
489
+ client_id: client_id,
490
+ redirect_uri: redirect_uri,
491
+ response_type: response_type
492
+ }
493
+ request[:organization_id] = organization_id unless organization_id.nil?
494
+ request[:member_id] = member_id unless member_id.nil?
495
+ request[:session_token] = session_token unless session_token.nil?
496
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
497
+ request[:prompt] = prompt unless prompt.nil?
498
+ request[:state] = state unless state.nil?
499
+ request[:nonce] = nonce unless nonce.nil?
500
+ request[:code_challenge] = code_challenge unless code_challenge.nil?
501
+
502
+ post_request('/v1/b2b/idp/oauth/authorize', request, headers)
503
+ end
504
+ end
265
505
  end
266
506
  end
@@ -1030,7 +1030,7 @@ module StytchB2B
1030
1030
  # If this field is provided and a session header is passed into the request, the Member Session must have permission to perform the `update.settings.default-mfa-method` action on the `stytch.member` Resource. Alternatively, if the Member Session matches the Member associated with the `member_id` passed in the request, the authorization check will also allow a Member Session that has permission to perform the `update.settings.default-mfa-method` action on the `stytch.self` Resource.
1031
1031
  # The type of this field is nilable +String+.
1032
1032
  # email_address::
1033
- # Updates the Member's `email_address`, if provided.
1033
+ # Updates the Member's `email_address`, if provided. This will clear any existing passwords and require re-verification of the new email address.
1034
1034
  # If a Member's email address is changed, other Members in the same Organization cannot use the old email address, although the Member may update back to their old email address.
1035
1035
  # A Member's email address can only be useable again by other Members if the Member is deleted.
1036
1036
  #
@@ -436,15 +436,15 @@ module StytchB2B
436
436
  # 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.
437
437
  #
438
438
  # == Parameters:
439
- # organization_id::
440
- # The organization ID that the session should be authenticated in.
441
- # The type of this field is +String+.
442
439
  # profile_id::
443
440
  # The ID of the trusted auth token profile to use for attestation.
444
441
  # The type of this field is +String+.
445
442
  # token::
446
443
  # The trusted auth token to authenticate.
447
444
  # The type of this field is +String+.
445
+ # organization_id::
446
+ # The organization ID that the session should be authenticated in.
447
+ # The type of this field is nilable +String+.
448
448
  # session_duration_minutes::
449
449
  # Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist,
450
450
  # returning both an opaque `session_token` and `session_jwt` for this session. Remember that the `session_jwt` will have a fixed lifetime of
@@ -503,9 +503,9 @@ module StytchB2B
503
503
  # If a valid `telemetry_id` was passed in the request and the [Fingerprint Lookup API](https://stytch.com/docs/fraud/api/fingerprint-lookup) returned results, the `member_device` response field will contain information about the member's device attributes.
504
504
  # The type of this field is nilable +DeviceInfo+ (+object+).
505
505
  def attest(
506
- organization_id:,
507
506
  profile_id:,
508
507
  token:,
508
+ organization_id: nil,
509
509
  session_duration_minutes: nil,
510
510
  session_custom_claims: nil,
511
511
  session_token: nil,
@@ -514,10 +514,10 @@ module StytchB2B
514
514
  )
515
515
  headers = {}
516
516
  request = {
517
- organization_id: organization_id,
518
517
  profile_id: profile_id,
519
518
  token: token
520
519
  }
520
+ request[:organization_id] = organization_id unless organization_id.nil?
521
521
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
522
522
  request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
523
523
  request[:session_token] = session_token unless session_token.nil?
data/lib/stytch/client.rb CHANGED
@@ -28,7 +28,7 @@ module Stytch
28
28
  class Client
29
29
  ENVIRONMENTS = %i[live test].freeze
30
30
 
31
- attr_reader :connected_app, :crypto_wallets, :fraud, :impersonation, :m2m, :magic_links, :oauth, :otps, :passwords, :project, :rbac, :sessions, :totps, :users, :webauthn, :idp
31
+ attr_reader :connected_app, :crypto_wallets, :fraud, :idp, :impersonation, :m2m, :magic_links, :oauth, :otps, :passwords, :project, :rbac, :sessions, :totps, :users, :webauthn
32
32
 
33
33
  def initialize(project_id:, secret:, env: nil, fraud_env: nil, &block)
34
34
  @api_host = api_host(env, project_id)
@@ -41,11 +41,11 @@ module Stytch
41
41
 
42
42
  rbac = Stytch::RBAC.new(@connection)
43
43
  @policy_cache = Stytch::PolicyCache.new(rbac_client: rbac)
44
- @idp = Stytch::IDP.new(@connection, @project_id, @policy_cache)
45
44
 
46
45
  @connected_app = Stytch::ConnectedApp.new(@connection)
47
46
  @crypto_wallets = Stytch::CryptoWallets.new(@connection)
48
47
  @fraud = Stytch::Fraud.new(@fraud_connection)
48
+ @idp = Stytch::IDP.new(@connection, @project_id, @policy_cache)
49
49
  @impersonation = Stytch::Impersonation.new(@connection)
50
50
  @m2m = Stytch::M2M.new(@connection, @project_id, @is_b2b_client)
51
51
  @magic_links = Stytch::MagicLinks.new(@connection)
data/lib/stytch/idp.rb CHANGED
@@ -1,36 +1,46 @@
1
1
  # frozen_string_literal: true
2
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
+
3
9
  require 'jwt'
4
10
  require 'json/jwt'
5
11
  require_relative 'errors'
6
12
  require_relative 'request_helper'
7
- require_relative 'rbac_local'
8
13
 
9
14
  module Stytch
10
15
  class IDP
11
16
  include Stytch::RequestHelper
17
+ attr_reader :oauth
12
18
 
13
19
  def initialize(connection, project_id, policy_cache)
14
20
  @connection = connection
15
- @project_id = project_id
21
+
22
+ @oauth = Stytch::IDP::OAuth.new(@connection)
16
23
  @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
- ]
24
+ @project_id = project_id
25
+ @cache_last_update = 0
26
+ @jwks_loader = lambda do |options|
27
+ @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - 300
28
+ @cached_keys ||= begin
29
+ @cache_last_update = Time.now.to_i
30
+ keys = []
31
+ get_jwks(project_id: @project_id)['keys'].each do |r|
32
+ keys << r
33
+ end
34
+ { keys: keys }
35
+ end
36
+ end
32
37
  end
33
38
 
39
+ # MANUAL(IDP::introspect_token_network)(SERVICE_METHOD)
40
+ # ADDIMPORT: require 'jwt'
41
+ # ADDIMPORT: require 'json/jwt'
42
+ # ADDIMPORT: require_relative 'errors'
43
+
34
44
  # Introspects a token JWT from an authorization code response.
35
45
  # Access tokens are JWTs signed with the project's JWKs. Refresh tokens are opaque tokens.
36
46
  # Access tokens contain a standard set of claims as well as any custom claims generated from templates.
@@ -102,7 +112,7 @@ module Stytch
102
112
  jwt_response = res
103
113
  return nil unless jwt_response['active']
104
114
 
105
- custom_claims = res.reject { |k, _| @non_custom_claim_keys.include?(k) }
115
+ custom_claims = res.reject { |k, _| non_custom_claim_keys.include?(k) }
106
116
  scope = jwt_response['scope']
107
117
 
108
118
  if authorization_check
@@ -171,18 +181,6 @@ module Stytch
171
181
  authorization_check: nil
172
182
  )
173
183
  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
184
  begin
187
185
  decoded_jwt = JWT.decode(
188
186
  access_token,
@@ -190,14 +188,14 @@ module Stytch
190
188
  true,
191
189
  {
192
190
  algorithms: ['RS256'],
193
- jwks: jwks_loader,
191
+ jwks: @jwks_loader,
194
192
  iss: ["stytch.com/#{@project_id}", @connection.url_prefix],
195
193
  aud: @project_id
196
194
  }
197
195
  )[0]
198
196
 
199
197
  generic_claims = decoded_jwt
200
- custom_claims = generic_claims.reject { |k, _| @non_custom_claim_keys.include?(k) }
198
+ custom_claims = generic_claims.reject { |k, _| non_custom_claim_keys.include?(k) }
201
199
  scope = generic_claims[scope_claim]
202
200
 
203
201
  if authorization_check
@@ -231,6 +229,26 @@ module Stytch
231
229
  end
232
230
  end
233
231
 
232
+ private
233
+
234
+ def non_custom_claim_keys
235
+ %w[
236
+ aud
237
+ exp
238
+ iat
239
+ iss
240
+ jti
241
+ nbf
242
+ sub
243
+ active
244
+ client_id
245
+ request_id
246
+ scope
247
+ status_code
248
+ token_type
249
+ ]
250
+ end
251
+
234
252
  # Gets the JWKS for the project.
235
253
  #
236
254
  # == Parameters:
@@ -247,5 +265,213 @@ module Stytch
247
265
  request = request_with_query_params("/v1/sessions/jwks/#{project_id}", query_params)
248
266
  get_request(request, headers)
249
267
  end
268
+
269
+ # ENDMANUAL(IDP::introspect_token_network)
270
+
271
+ class OAuth
272
+ include Stytch::RequestHelper
273
+
274
+ def initialize(connection)
275
+ @connection = connection
276
+ end
277
+
278
+ # Initiates a request for authorization of a Connected App to access a User's account.
279
+ #
280
+ # Call this endpoint using the query parameters from an OAuth Authorization request.
281
+ # This endpoint validates various fields (`scope`, `client_id`, `redirect_uri`, `prompt`, etc...) are correct and returns
282
+ # relevant information for rendering an OAuth Consent Screen.
283
+ #
284
+ # This endpoint returns:
285
+ # - A public representation of the Connected App requesting authorization
286
+ # - Whether _explicit_ user consent must be granted before proceeding with the authorization
287
+ # - A list of scopes the user has the ability to grant the Connected App
288
+ #
289
+ # Use this response to prompt the user for consent (if necessary) before calling the [Submit OAuth Authorization](https://stytch.com/docs/api/connected-apps-oauth-authorize) endpoint.
290
+ #
291
+ # Exactly one of the following must be provided to identify the user granting authorization:
292
+ # - `user_id`
293
+ # - `session_token`
294
+ # - `session_jwt`
295
+ #
296
+ # If a `session_token` or `session_jwt` is passed, the OAuth Authorization will be linked to the user's session for tracking purposes.
297
+ # One of these fields must be used if the Connected App intends to complete the [Exchange Access Token](https://stytch.com/docs/api/connected-app-access-token-exchange) flow.
298
+ #
299
+ # == Parameters:
300
+ # client_id::
301
+ # The ID of the Connected App client.
302
+ # The type of this field is +String+.
303
+ # redirect_uri::
304
+ # The callback URI used to redirect the user after authentication. This is the same URI provided at the start of the OAuth flow. This field is required when using the `authorization_code` grant.
305
+ # The type of this field is +String+.
306
+ # response_type::
307
+ # The OAuth 2.0 response type. For authorization code flows this value is `code`.
308
+ # The type of this field is +String+.
309
+ # scopes::
310
+ # An array of scopes requested by the client.
311
+ # The type of this field is list of +String+.
312
+ # user_id::
313
+ # The unique ID of a specific User. You may use an `external_id` here if one is set for the user.
314
+ # The type of this field is nilable +String+.
315
+ # session_token::
316
+ # The `session_token` associated with a User's existing Session.
317
+ # The type of this field is nilable +String+.
318
+ # session_jwt::
319
+ # The `session_jwt` associated with a User's existing Session.
320
+ # The type of this field is nilable +String+.
321
+ # prompt::
322
+ # Space separated list that specifies how the Authorization Server should prompt the user for reauthentication and consent. Only `consent` is supported today.
323
+ # The type of this field is nilable +String+.
324
+ #
325
+ # == Returns:
326
+ # An object with the following fields:
327
+ # request_id::
328
+ # 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.
329
+ # The type of this field is +String+.
330
+ # user_id::
331
+ # The unique ID of the affected User.
332
+ # The type of this field is +String+.
333
+ # user::
334
+ # The `user` object affected by this API call. See the [Get user endpoint](https://stytch.com/docs/api/get-user) for complete response field details.
335
+ # The type of this field is +User+ (+object+).
336
+ # client::
337
+ # (no documentation yet)
338
+ # The type of this field is +ConnectedAppPublic+ (+object+).
339
+ # consent_required::
340
+ # Whether the user must provide explicit consent for the authorization request.
341
+ # The type of this field is +Boolean+.
342
+ # scope_results::
343
+ # Details about each requested scope.
344
+ # The type of this field is list of +ScopeResult+ (+object+).
345
+ # status_code::
346
+ # (no documentation yet)
347
+ # The type of this field is +Integer+.
348
+ def authorize_start(
349
+ client_id:,
350
+ redirect_uri:,
351
+ response_type:,
352
+ scopes:,
353
+ user_id: nil,
354
+ session_token: nil,
355
+ session_jwt: nil,
356
+ prompt: nil
357
+ )
358
+ headers = {}
359
+ request = {
360
+ client_id: client_id,
361
+ redirect_uri: redirect_uri,
362
+ response_type: response_type,
363
+ scopes: scopes
364
+ }
365
+ request[:user_id] = user_id unless user_id.nil?
366
+ request[:session_token] = session_token unless session_token.nil?
367
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
368
+ request[:prompt] = prompt unless prompt.nil?
369
+
370
+ post_request('/v1/idp/oauth/authorize/start', request, headers)
371
+ end
372
+
373
+ # Completes a request for authorization of a Connected App to access a User's account.
374
+ #
375
+ # Call this endpoint using the query parameters from an OAuth Authorization request, after previously validating those parameters using the
376
+ # [Preflight Check](https://stytch.com/docs/api/connected-apps-oauth-authorize-start) API.
377
+ # Note that this endpoint takes in a few additional parameters the preflight check does not- `state`, `nonce`, and `code_challenge`.
378
+ #
379
+ # If the authorization was successful, the `redirect_uri` will contain a valid `authorization_code` embedded as a query parameter.
380
+ # If the authorization was unsuccessful, the `redirect_uri` will contain an OAuth2.1 `error_code`.
381
+ # In both cases, redirect the user to the location for the response to be consumed by the Connected App.
382
+ #
383
+ # Exactly one of the following must be provided to identify the user granting authorization:
384
+ # - `user_id`
385
+ # - `session_token`
386
+ # - `session_jwt`
387
+ #
388
+ # If a `session_token` or `session_jwt` is passed, the OAuth Authorization will be linked to the user's session for tracking purposes.
389
+ # One of these fields must be used if the Connected App intends to complete the [Exchange Access Token](https://stytch.com/docs/api/connected-app-access-token-exchange) flow.
390
+ #
391
+ # == Parameters:
392
+ # consent_granted::
393
+ # Indicates whether the user granted the requested scopes.
394
+ # The type of this field is +Boolean+.
395
+ # scopes::
396
+ # An array of scopes requested by the client.
397
+ # The type of this field is list of +String+.
398
+ # client_id::
399
+ # The ID of the Connected App client.
400
+ # The type of this field is +String+.
401
+ # redirect_uri::
402
+ # The callback URI used to redirect the user after authentication. This is the same URI provided at the start of the OAuth flow. This field is required when using the `authorization_code` grant.
403
+ # The type of this field is +String+.
404
+ # response_type::
405
+ # The OAuth 2.0 response type. For authorization code flows this value is `code`.
406
+ # The type of this field is +String+.
407
+ # user_id::
408
+ # The unique ID of a specific User. You may use an `external_id` here if one is set for the user.
409
+ # The type of this field is nilable +String+.
410
+ # session_token::
411
+ # The `session_token` associated with a User's existing Session.
412
+ # The type of this field is nilable +String+.
413
+ # session_jwt::
414
+ # The `session_jwt` associated with a User's existing Session.
415
+ # The type of this field is nilable +String+.
416
+ # prompt::
417
+ # Space separated list that specifies how the Authorization Server should prompt the user for reauthentication and consent. Only `consent` is supported today.
418
+ # The type of this field is nilable +String+.
419
+ # state::
420
+ # An opaque value used to maintain state between the request and callback.
421
+ # The type of this field is nilable +String+.
422
+ # nonce::
423
+ # A string used to associate a client session with an ID token to mitigate replay attacks.
424
+ # The type of this field is nilable +String+.
425
+ # code_challenge::
426
+ # A base64url encoded challenge derived from the code verifier for PKCE flows.
427
+ # The type of this field is nilable +String+.
428
+ #
429
+ # == Returns:
430
+ # An object with the following fields:
431
+ # request_id::
432
+ # 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.
433
+ # The type of this field is +String+.
434
+ # redirect_uri::
435
+ # The callback URI used to redirect the user after authentication. This is the same URI provided at the start of the OAuth flow. This field is required when using the `authorization_code` grant.
436
+ # The type of this field is +String+.
437
+ # status_code::
438
+ # (no documentation yet)
439
+ # The type of this field is +Integer+.
440
+ # authorization_code::
441
+ # A one-time use code that can be exchanged for tokens.
442
+ # The type of this field is nilable +String+.
443
+ def authorize(
444
+ consent_granted:,
445
+ scopes:,
446
+ client_id:,
447
+ redirect_uri:,
448
+ response_type:,
449
+ user_id: nil,
450
+ session_token: nil,
451
+ session_jwt: nil,
452
+ prompt: nil,
453
+ state: nil,
454
+ nonce: nil,
455
+ code_challenge: nil
456
+ )
457
+ headers = {}
458
+ request = {
459
+ consent_granted: consent_granted,
460
+ scopes: scopes,
461
+ client_id: client_id,
462
+ redirect_uri: redirect_uri,
463
+ response_type: response_type
464
+ }
465
+ request[:user_id] = user_id unless user_id.nil?
466
+ request[:session_token] = session_token unless session_token.nil?
467
+ request[:session_jwt] = session_jwt unless session_jwt.nil?
468
+ request[:prompt] = prompt unless prompt.nil?
469
+ request[:state] = state unless state.nil?
470
+ request[:nonce] = nonce unless nonce.nil?
471
+ request[:code_challenge] = code_challenge unless code_challenge.nil?
472
+
473
+ post_request('/v1/idp/oauth/authorize', request, headers)
474
+ end
475
+ end
250
476
  end
251
477
  end
@@ -327,7 +327,8 @@ module Stytch
327
327
  # If a new user is created, this will set an identifier that can be used in API calls wherever a user_id is expected. This is a string consisting of alphanumeric, `.`, `_`, `-`, or `|` characters with a maximum length of 128 characters.
328
328
  # The type of this field is nilable +String+.
329
329
  # roles::
330
- # (no documentation yet)
330
+ # Roles to explicitly assign to this User.
331
+ # See the [RBAC guide](https://stytch.com/docs/guides/rbac/role-assignment) for more information about role assignment.
331
332
  # The type of this field is nilable list of +String+.
332
333
  #
333
334
  # == Returns:
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stytch
4
- VERSION = '10.28.0'
4
+ VERSION = '10.29.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stytch
3
3
  version: !ruby/object:Gem::Version
4
- version: 10.28.0
4
+ version: 10.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - stytch
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-12 00:00:00.000000000 Z
11
+ date: 2025-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday