stytch 9.0.0 → 9.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5075518300c9fb8d82d98b80851825a1bfc72d61b637dbbbc54df07ad4b40f9
4
- data.tar.gz: e4abd2aec41c59f9d9605f0c6f43a62d5d4126991cfe89b5e96ad293a33922d5
3
+ metadata.gz: 054cdb1171eff8373e48ab9d77b74170bfaaabd4d77cb2c31657925aca44f632
4
+ data.tar.gz: 1a0ed91c5bf5ea60160e77402f8724517bbcf9aa10743a38efe83ac0c9b76785
5
5
  SHA512:
6
- metadata.gz: 29a88d3485f4cabefe89721a83506e0dcbe64d8864780eec0508da70f5d308a204fa427cd0fb8e0c725c07bb45c20f37d182cb3f75e2694536f562b863a35ce9
7
- data.tar.gz: 6eaf332fea8ba9eb38da3af8d6eea7376b947a97e93f6478c0e149cc44d35236d9f148ee42e7179fb83ec6fa743076c806a7fc5b6c9e19e584763a3d2a5a2297
6
+ metadata.gz: 3433370d4868be620088af753c48c378e3d571458240698764813b8e581f935bd1975d8fca86416ffccb3644292f0d1cfa29343b65431462a186573b2eeaa51e
7
+ data.tar.gz: 5d139670d4e0a04ac7c6d500aa96e18c8b1c42cea8d144187b1396af840d4a103e343f2f95f8445b0f6964767933b057bd82c48e5ac1b98973289c4863ae6d63
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  require:
2
- - rubocop-rspec
2
+ - rubocop-rspec
3
3
 
4
4
  AllCops:
5
5
  NewCops: disable
@@ -7,6 +7,7 @@ AllCops:
7
7
  TargetRubyVersion: 2.7
8
8
 
9
9
  Layout/LineLength: { Enabled: false }
10
+ LeadingCommentSpace: { Enabled: false }
10
11
 
11
12
  Metrics: { Enabled: false }
12
13
 
data/DEVELOPMENT.md CHANGED
@@ -18,4 +18,4 @@ If you have non-trivial changes you'd like us to incorporate, please open an iss
18
18
  When you're ready for someone to look at your issue or PR, assign `@stytchauth/client-libraries` (GitHub should do this automatically). If we don't acknowledge it within one business day, please escalate it by tagging `@stytchauth/engineering` in a comment or letting us know in [Slack].
19
19
 
20
20
  [Bundler]: https://bundler.io/
21
- [Slack]: https://stytch.slack.com/join/shared_invite/zt-2f0fi1ruu-ub~HGouWRmPARM1MTwPESA
21
+ [Slack]: https://stytch.com/docs/resources/support/overview
data/README.md CHANGED
@@ -25,6 +25,9 @@ Or install it yourself as:
25
25
  You can find your API credentials in the [Stytch Dashboard](https://stytch.com/dashboard/api-keys).
26
26
 
27
27
  This client library supports all Stytch's live products:
28
+
29
+ ### B2C
30
+
28
31
  - [x] [Email Magic Links](https://stytch.com/docs/api/send-by-email)
29
32
  - [x] [Embeddable Magic Links](https://stytch.com/docs/guides/magic-links/embeddable-magic-links/api)
30
33
  - [x] [OAuth logins](https://stytch.com/docs/guides/oauth/idp-overview)
@@ -37,7 +40,18 @@ This client library supports all Stytch's live products:
37
40
  - [x] [Crypto wallets](https://stytch.com/docs/guides/web3/api)
38
41
  - [x] [Passwords](https://stytch.com/docs/guides/passwords/api)
39
42
 
40
- ### Example usage
43
+ ### B2B
44
+
45
+ - [x] [Organizations](https://stytch.com/docs/b2b/api/organization-object)
46
+ - [x] [Members](https://stytch.com/docs/b2b/api/member-object)
47
+ - [x] [Email Magic Links](https://stytch.com/docs/b2b/api/send-login-signup-email)
48
+ - [x] [OAuth logins](https://stytch.com/docs/b2b/api/oauth-google-start)
49
+ - [x] [Session Management](https://stytch.com/docs/b2b/api/session-object)
50
+ - [x] [Single-Sign On](https://stytch.com/docs/b2b/api/sso-authenticate-start)
51
+ - [x] [Discovery](https://stytch.com/docs/b2b/api/discovered-organization-object)
52
+ - [x] [Passwords](https://stytch.com/docs/b2b/api/passwords-authenticate)
53
+
54
+ ### Example B2C usage
41
55
  Create an API client:
42
56
  ```ruby
43
57
  client = Stytch::Client.new(
@@ -61,6 +75,41 @@ client.magic_links.authenticate(
61
75
  )
62
76
  ```
63
77
 
78
+ ### Example B2B usage
79
+
80
+ Create an API client:
81
+ ```ruby
82
+ require 'stytch'
83
+
84
+ client = StytchB2B::Client.new(
85
+ project_id: "project-test-uuid",
86
+ secret: "secret-test-uuid"
87
+ )
88
+ ```
89
+
90
+ Create an organization
91
+
92
+ ```ruby
93
+ resp = client.organizations.create(
94
+ organization_name: 'Example Org Inc.',
95
+ organization_slug: 'example-org'
96
+ )
97
+
98
+ puts resp
99
+ ```
100
+
101
+ Log the first user into the organization
102
+
103
+ ```ruby
104
+ resp = client.magic_links.email.login_or_signup(
105
+ organization_id: 'organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931',
106
+ email_address: 'sandbox@stytch.com'
107
+ )
108
+
109
+ puts resp
110
+ ```
111
+
112
+
64
113
  ## Handling Errors
65
114
 
66
115
  When possible the response will contain an `error_type` and an `error_message` that can be used to distinguish errors.
@@ -77,7 +126,7 @@ Follow one of the [integration guides](https://stytch.com/docs/guides) or start
77
126
 
78
127
  If you've found a bug, [open an issue](https://github.com/stytchauth/stytch-ruby/issues/new)!
79
128
 
80
- If you have questions or want help troubleshooting, join us in [Slack](https://stytch.slack.com/join/shared_invite/zt-2f0fi1ruu-ub~HGouWRmPARM1MTwPESA) or email support@stytch.com.
129
+ If you have questions or want help troubleshooting, join us in [Slack](https://stytch.com/docs/resources/support/overview) or email support@stytch.com.
81
130
 
82
131
  If you've found a security vulnerability, please follow our [responsible disclosure instructions](https://stytch.com/docs/resources/security-and-trust/security#:~:text=Responsible%20disclosure%20program).
83
132
 
@@ -26,6 +26,7 @@ module StytchB2B
26
26
  @api_host = api_host(env, project_id)
27
27
  @project_id = project_id
28
28
  @secret = secret
29
+ @is_b2b_client = true
29
30
 
30
31
  create_connection(&block)
31
32
 
@@ -33,7 +34,7 @@ module StytchB2B
33
34
  @policy_cache = StytchB2B::PolicyCache.new(rbac_client: rbac)
34
35
 
35
36
  @discovery = StytchB2B::Discovery.new(@connection)
36
- @m2m = Stytch::M2M.new(@connection, @project_id)
37
+ @m2m = Stytch::M2M.new(@connection, @project_id, @is_b2b_client)
37
38
  @magic_links = StytchB2B::MagicLinks.new(@connection)
38
39
  @oauth = StytchB2B::OAuth.new(@connection)
39
40
  @otps = StytchB2B::OTPs.new(@connection)
@@ -268,7 +268,6 @@ module StytchB2B
268
268
  # Send an invite email to a new Member to join an Organization. The Member will be created with an `invited` status until they successfully authenticate. Sending invites to `pending` Members will update their status to `invited`. Sending invites to already `active` Members will return an error.
269
269
  #
270
270
  # The magic link invite will be valid for 1 week.
271
- # /%}
272
271
  #
273
272
  # == Parameters:
274
273
  # organization_id::
@@ -230,18 +230,6 @@ module StytchB2B
230
230
  #
231
231
  # *See the [Organization authentication settings](https://stytch.com/docs/b2b/api/org-auth-settings) resource to learn more about fields like `email_jit_provisioning`, `email_invites`, `sso_jit_provisioning`, etc., and their behaviors.
232
232
  #
233
- # Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in
234
- # a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the
235
- # Member Session has the necessary permissions. The specific permissions needed depend on which of the optional fields
236
- # are passed in the request. For example, if the `organization_name` argument is provided, the Member Session must have
237
- # permission to perform the `update.info.name` action on the `stytch.organization` Resource.
238
- #
239
- # If the Member Session does not contain a Role that satisfies the requested permissions, or if the Member's Organization
240
- # does not match the `organization_id` passed in the request, a 403 error will be thrown. Otherwise, the request will
241
- # proceed as normal.
242
- #
243
- # To learn more about our RBAC implementation, see our [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/overview).
244
- #
245
233
  # == Parameters:
246
234
  # organization_id::
247
235
  # 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.
@@ -429,7 +417,7 @@ module StytchB2B
429
417
  put_request("/v1/b2b/organizations/#{organization_id}", request, headers)
430
418
  end
431
419
 
432
- # Deletes an Organization specified by `organization_id`. All Members of the Organization will also be deleted. /%}
420
+ # Deletes an Organization specified by `organization_id`. All Members of the Organization will also be deleted.
433
421
  #
434
422
  # == Parameters:
435
423
  # organization_id::
@@ -673,18 +661,6 @@ module StytchB2B
673
661
 
674
662
  # Updates a Member specified by `organization_id` and `member_id`.
675
663
  #
676
- # Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in
677
- # a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the
678
- # Member Session has the necessary permissions. The specific permissions needed depend on which of the optional fields
679
- # are passed in the request. For example, if the `organization_name` argument is provided, the Member Session must have
680
- # permission to perform the `update.info.name` action on the `stytch.organization` Resource.
681
- #
682
- # If the Member Session does not contain a Role that satisfies the requested permissions, or if the Member's Organization
683
- # does not match the `organization_id` passed in the request, a 403 error will be thrown. Otherwise, the request will
684
- # proceed as normal.
685
- #
686
- # To learn more about our RBAC implementation, see our [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/overview).
687
- #
688
664
  # == Parameters:
689
665
  # organization_id::
690
666
  # 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.
@@ -806,7 +782,7 @@ module StytchB2B
806
782
  put_request("/v1/b2b/organizations/#{organization_id}/members/#{member_id}", request, headers)
807
783
  end
808
784
 
809
- # Deletes a Member specified by `organization_id` and `member_id`. /%}
785
+ # Deletes a Member specified by `organization_id` and `member_id`.
810
786
  #
811
787
  # == Parameters:
812
788
  # organization_id::
@@ -840,7 +816,7 @@ module StytchB2B
840
816
  delete_request("/v1/b2b/organizations/#{organization_id}/members/#{member_id}", headers)
841
817
  end
842
818
 
843
- # Reactivates a deleted Member's status and its associated email status (if applicable) to active, specified by `organization_id` and `member_id`. /%}
819
+ # Reactivates a deleted Member's status and its associated email status (if applicable) to active, specified by `organization_id` and `member_id`.
844
820
  #
845
821
  # == Parameters:
846
822
  # organization_id::
@@ -889,7 +865,6 @@ module StytchB2B
889
865
  # Existing Member Sessions that include a phone number authentication factor will not be revoked if the phone number is deleted, and MFA will not be enforced until the Member logs in again.
890
866
  # If you wish to enforce MFA immediately after a phone number is deleted, you can do so by prompting the Member to enter a new phone number
891
867
  # and calling the [OTP SMS send](https://stytch.com/docs/b2b/api/otp-sms-send) endpoint, then calling the [OTP SMS Authenticate](https://stytch.com/docs/b2b/api/authenticate-otp-sms) endpoint.
892
- # /%}
893
868
  #
894
869
  # == Parameters:
895
870
  # organization_id::
@@ -934,7 +909,6 @@ module StytchB2B
934
909
  # To mint a new registration for a Member, you must first call this endpoint to delete the existing registration.
935
910
  #
936
911
  # Existing Member Sessions that include the TOTP authentication factor will not be revoked if the registration is deleted, and MFA will not be enforced until the Member logs in again.
937
- # /%}
938
912
  #
939
913
  # == Parameters:
940
914
  # organization_id::
@@ -978,18 +952,6 @@ module StytchB2B
978
952
  #
979
953
  # *All fuzzy search filters require a minimum of three characters.
980
954
  #
981
- # Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in
982
- # a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the
983
- # Member Session has permission to perform the `search` action on the `stytch.member` Resource. In addition, enforcing
984
- # RBAC on this endpoint means that you may only search for Members within the calling Member's Organization, so the
985
- # `organization_ids` argument may only contain the `organization_id` of the Member Session passed in the header.
986
- #
987
- # If the Member Session does not contain a Role that satisfies the requested permission, or if the `organization_ids`
988
- # argument contains an `organization_id` that the Member Session does not belong to, a 403 error will be thrown.
989
- # Otherwise, the request will proceed as normal.
990
- #
991
- # To learn more about our RBAC implementation, see our [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/overview).
992
- #
993
955
  # == Parameters:
994
956
  # organization_ids::
995
957
  # An array of organization_ids. At least one value is required.
@@ -1043,7 +1005,7 @@ module StytchB2B
1043
1005
  post_request('/v1/b2b/organizations/members/search', request, headers)
1044
1006
  end
1045
1007
 
1046
- # Delete a Member's password. /%}
1008
+ # Delete a Member's password.
1047
1009
  #
1048
1010
  # == Parameters:
1049
1011
  # organization_id::
@@ -1116,7 +1078,7 @@ module StytchB2B
1116
1078
  get_request(request, headers)
1117
1079
  end
1118
1080
 
1119
- # Creates a Member. An `organization_id` and `email_address` are required. /%}
1081
+ # Creates a Member. An `organization_id` and `email_address` are required.
1120
1082
  #
1121
1083
  # == Parameters:
1122
1084
  # organization_id::
@@ -34,7 +34,7 @@ module StytchB2B
34
34
  #
35
35
  # == Parameters:
36
36
  # password::
37
- # The password to authenticate.
37
+ # The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
38
38
  # The type of this field is +String+.
39
39
  # email_address::
40
40
  # The email address of the Member.
@@ -89,6 +89,8 @@ module StytchB2B
89
89
 
90
90
  # Adds an existing password to a member's email that doesn't have a password yet. We support migrating members from passwords stored with bcrypt, scrypt, argon2, MD-5, SHA-1, and PBKDF2. This endpoint has a rate limit of 100 requests per second.
91
91
  #
92
+ # The member's email will be marked as verified when you use this endpoint.
93
+ #
92
94
  # == Parameters:
93
95
  # email_address::
94
96
  # The email address of the Member.
@@ -219,7 +221,7 @@ module StytchB2B
219
221
  # The email address of the Member.
220
222
  # The type of this field is +String+.
221
223
  # password::
222
- # The password to authenticate.
224
+ # The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
223
225
  # The type of this field is +String+.
224
226
  # session_token::
225
227
  # A secret token for a given Stytch Session.
@@ -427,12 +429,14 @@ module StytchB2B
427
429
  #
428
430
  # If a valid `session_token` or `session_jwt` is passed in, the Member will not be required to complete an MFA step.
429
431
  #
432
+ # Note that a successful password reset by email will revoke all active sessions for the `member_id`.
433
+ #
430
434
  # == Parameters:
431
435
  # password_reset_token::
432
436
  # The password reset token to authenticate.
433
437
  # The type of this field is +String+.
434
438
  # password::
435
- # The password to reset.
439
+ # The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
436
440
  # The type of this field is +String+.
437
441
  # session_token::
438
442
  # Reuse an existing session instead of creating a new one. If you provide a `session_token`, Stytch will update the session.
@@ -557,12 +561,14 @@ module StytchB2B
557
561
 
558
562
  # Reset the Member's password using their existing session. The endpoint will error if the session does not contain an authentication factor that has been issued within the last 5 minutes. Either `session_token` or `session_jwt` should be provided.
559
563
  #
564
+ # Note that a successful password reset via an existing session will revoke all active sessions for the `member_id`, except for the one used during the reset flow.
565
+ #
560
566
  # == Parameters:
561
567
  # organization_id::
562
568
  # 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.
563
569
  # The type of this field is +String+.
564
570
  # password::
565
- # The password to authenticate.
571
+ # The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
566
572
  # The type of this field is +String+.
567
573
  # session_token::
568
574
  # A secret token for a given Stytch Session.
@@ -677,15 +683,17 @@ module StytchB2B
677
683
  #
678
684
  # If a valid `session_token` or `session_jwt` is passed in, the Member will not be required to complete an MFA step.
679
685
  #
686
+ # Note that a successful password reset via an existing password will revoke all active sessions for the `member_id`.
687
+ #
680
688
  # == Parameters:
681
689
  # email_address::
682
690
  # The email address of the Member.
683
691
  # The type of this field is +String+.
684
692
  # existing_password::
685
- # The member's current password that they supplied.
693
+ # The Member's current password that they supplied.
686
694
  # The type of this field is +String+.
687
695
  # new_password::
688
- # The member's elected new password.
696
+ # The Member's elected new password.
689
697
  # The type of this field is +String+.
690
698
  # organization_id::
691
699
  # 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.
@@ -115,6 +115,25 @@ module StytchB2B
115
115
  end
116
116
  end
117
117
 
118
+ class GetGroupsRequestOptions
119
+ # Optional authorization object.
120
+ # Pass in an active Stytch Member session token or session JWT and the request
121
+ # will be run using that member's permissions.
122
+ attr_accessor :authorization
123
+
124
+ def initialize(
125
+ authorization: nil
126
+ )
127
+ @authorization = authorization
128
+ end
129
+
130
+ def to_headers
131
+ headers = {}
132
+ headers.merge!(@authorization.to_headers) if authorization
133
+ headers
134
+ end
135
+ end
136
+
118
137
  class CreateRequestOptions
119
138
  # Optional authorization object.
120
139
  # Pass in an active Stytch Member session token or session JWT and the request
@@ -159,7 +178,7 @@ module StytchB2B
159
178
  @connection = connection
160
179
  end
161
180
 
162
- # Update a SCIM Connection. /%}
181
+ # Update a SCIM Connection.
163
182
  #
164
183
  # == Parameters:
165
184
  # organization_id::
@@ -210,7 +229,7 @@ module StytchB2B
210
229
  put_request("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}", request, headers)
211
230
  end
212
231
 
213
- # Deletes a SCIM Connection. /%}
232
+ # Deletes a SCIM Connection.
214
233
  #
215
234
  # == Parameters:
216
235
  # organization_id::
@@ -244,7 +263,7 @@ module StytchB2B
244
263
  delete_request("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}", headers)
245
264
  end
246
265
 
247
- # Start a SCIM token rotation. /%}
266
+ # Start a SCIM token rotation.
248
267
  #
249
268
  # == Parameters:
250
269
  # organization_id::
@@ -280,7 +299,7 @@ module StytchB2B
280
299
  post_request("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}/rotate/start", request, headers)
281
300
  end
282
301
 
283
- # Completes a SCIM token rotation. This will complete the current token rotation process and update the active token to be the new token supplied in the [start SCIM token rotation](https://stytch.com/docs/b2b/api/scim-rotate-token-start) response. /%}
302
+ # Completes a SCIM token rotation. This will complete the current token rotation process and update the active token to be the new token supplied in the [start SCIM token rotation](https://stytch.com/docs/b2b/api/scim-rotate-token-start) response.
284
303
  #
285
304
  # == Parameters:
286
305
  # organization_id::
@@ -316,7 +335,7 @@ module StytchB2B
316
335
  post_request("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}/rotate/complete", request, headers)
317
336
  end
318
337
 
319
- # Cancel a SCIM token rotation. This will cancel the current token rotation process, keeping the original token active. /%}
338
+ # Cancel a SCIM token rotation. This will cancel the current token rotation process, keeping the original token active.
320
339
  #
321
340
  # == Parameters:
322
341
  # organization_id::
@@ -352,7 +371,54 @@ module StytchB2B
352
371
  post_request("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}/rotate/cancel", request, headers)
353
372
  end
354
373
 
355
- # Create a new SCIM Connection. /%}
374
+ # Gets a paginated list of all SCIM Groups associated with a given Connection.
375
+ #
376
+ # == Parameters:
377
+ # organization_id::
378
+ # 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.
379
+ # The type of this field is +String+.
380
+ # connection_id::
381
+ # The ID of the SCIM connection.
382
+ # The type of this field is +String+.
383
+ # cursor::
384
+ # The `cursor` field allows you to paginate through your results. Each result array is limited to 1000 results. If your query returns more than 1000 results, you will need to paginate the responses using the `cursor`. If you receive a response that includes a non-null `next_cursor` in the `results_metadata` object, repeat the search call with the `next_cursor` value set to the `cursor` field to retrieve the next page of results. Continue to make search calls until the `next_cursor` in the response is null.
385
+ # The type of this field is nilable +String+.
386
+ # limit::
387
+ # The number of search results to return per page. The default limit is 100. A maximum of 1000 results can be returned by a single search request. If the total size of your result set is greater than one page size, you must paginate the response. See the `cursor` field.
388
+ # The type of this field is nilable +Integer+.
389
+ #
390
+ # == Returns:
391
+ # An object with the following fields:
392
+ # scim_groups::
393
+ # A list of SCIM Connection Groups belonging to the connection.
394
+ # The type of this field is list of +SCIMGroup+ (+object+).
395
+ # status_code::
396
+ # (no documentation yet)
397
+ # The type of this field is +Integer+.
398
+ # next_cursor::
399
+ # The `next_cursor` string is returned when your search result contains more than one page of results. This value is passed into your next search call in the `cursor` field.
400
+ # The type of this field is nilable +String+.
401
+ #
402
+ # == Method Options:
403
+ # This method supports an optional +StytchB2B::SCIM::Connection::GetGroupsRequestOptions+ object which will modify the headers sent in the HTTP request.
404
+ def get_groups(
405
+ organization_id:,
406
+ connection_id:,
407
+ cursor: nil,
408
+ limit: nil,
409
+ method_options: nil
410
+ )
411
+ headers = {}
412
+ headers = headers.merge(method_options.to_headers) unless method_options.nil?
413
+ query_params = {
414
+ cursor: cursor,
415
+ limit: limit
416
+ }
417
+ request = request_with_query_params("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}", query_params)
418
+ get_request(request, headers)
419
+ end
420
+
421
+ # Create a new SCIM Connection.
356
422
  #
357
423
  # == Parameters:
358
424
  # organization_id::
@@ -394,7 +460,7 @@ module StytchB2B
394
460
  post_request("/v1/b2b/scim/#{organization_id}/connection", request, headers)
395
461
  end
396
462
 
397
- # Get SCIM Connections. /%}
463
+ # Get SCIM Connections.
398
464
  #
399
465
  # == Parameters:
400
466
  # organization_id::
@@ -13,6 +13,25 @@ require_relative 'request_helper'
13
13
 
14
14
  module StytchB2B
15
15
  class Sessions
16
+ class RevokeRequestOptions
17
+ # Optional authorization object.
18
+ # Pass in an active Stytch Member session token or session JWT and the request
19
+ # will be run using that member's permissions.
20
+ attr_accessor :authorization
21
+
22
+ def initialize(
23
+ authorization: nil
24
+ )
25
+ @authorization = authorization
26
+ end
27
+
28
+ def to_headers
29
+ headers = {}
30
+ headers.merge!(@authorization.to_headers) if authorization
31
+ headers
32
+ end
33
+ end
34
+
16
35
  include Stytch::RequestHelper
17
36
 
18
37
  def initialize(connection, project_id, policy_cache)
@@ -70,7 +89,7 @@ module StytchB2B
70
89
 
71
90
  # Authenticates a Session and updates its lifetime by the specified `session_duration_minutes`. If the `session_duration_minutes` is not specified, a Session will not be extended. This endpoint requires either a `session_jwt` or `session_token` be included in the request. It will return an error if both are present.
72
91
  #
73
- # You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid. See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/using-jwts) guide for more information.
92
+ # You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid. See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/resources/using-jwts) guide for more information.
74
93
  #
75
94
  # If an `authorization_check` object is passed in, this method will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
76
95
  # In addition, the `organization_id` passed in the authorization check must match the Member's Organization.
@@ -189,13 +208,18 @@ module StytchB2B
189
208
  # status_code::
190
209
  # 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.
191
210
  # The type of this field is +Integer+.
211
+ #
212
+ # == Method Options:
213
+ # This method supports an optional +StytchB2B::Sessions::RevokeRequestOptions+ object which will modify the headers sent in the HTTP request.
192
214
  def revoke(
193
215
  member_session_id: nil,
194
216
  session_token: nil,
195
217
  session_jwt: nil,
196
- member_id: nil
218
+ member_id: nil,
219
+ method_options: nil
197
220
  )
198
221
  headers = {}
222
+ headers = headers.merge(method_options.to_headers) unless method_options.nil?
199
223
  request = {}
200
224
  request[:member_session_id] = member_session_id unless member_session_id.nil?
201
225
  request[:session_token] = session_token unless session_token.nil?
@@ -397,7 +421,7 @@ module StytchB2B
397
421
  #
398
422
  # If you're using your own JWT validation library, many have built-in support for JWKS rotation, and you'll just need to supply this API endpoint. If not, your application should decide which JWKS to use for validation by inspecting the `kid` value.
399
423
  #
400
- # See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/using-jwts) guide for more information.
424
+ # See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/resources/using-jwts) guide for more information.
401
425
  #
402
426
  # == Parameters:
403
427
  # project_id::
@@ -436,14 +460,17 @@ module StytchB2B
436
460
  # Note that the 'user_id' field of the returned session is DEPRECATED: Use member_id instead
437
461
  # This field will be removed in a future MAJOR release.
438
462
  # If max_token_age_seconds is not supplied 300 seconds will be used as the default.
463
+ # If clock_tolerance_seconds is not supplied 0 seconds will be used as the default.
439
464
  def authenticate_jwt(
440
465
  session_jwt,
441
466
  max_token_age_seconds: nil,
442
467
  session_duration_minutes: nil,
443
468
  session_custom_claims: nil,
444
- authorization_check: nil
469
+ authorization_check: nil,
470
+ clock_tolerance_seconds: nil
445
471
  )
446
472
  max_token_age_seconds = 300 if max_token_age_seconds.nil?
473
+ clock_tolerance_seconds = 0 if clock_tolerance_seconds.nil?
447
474
 
448
475
  if max_token_age_seconds == 0
449
476
  return authenticate(
@@ -454,7 +481,7 @@ module StytchB2B
454
481
  )
455
482
  end
456
483
 
457
- decoded_jwt = authenticate_jwt_local(session_jwt, max_token_age_seconds: max_token_age_seconds, authorization_check: authorization_check)
484
+ decoded_jwt = authenticate_jwt_local(session_jwt, max_token_age_seconds: max_token_age_seconds, authorization_check: authorization_check, clock_tolerance_seconds: clock_tolerance_seconds)
458
485
  return decoded_jwt unless decoded_jwt.nil?
459
486
 
460
487
  authenticate(
@@ -478,13 +505,15 @@ module StytchB2B
478
505
  # function to get the JWK
479
506
  # This method never authenticates a JWT directly with the API
480
507
  # If max_token_age_seconds is not supplied 300 seconds will be used as the default.
481
- def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil, authorization_check: nil)
508
+ # If clock_tolerance_seconds is not supplied 0 seconds will be used as the default.
509
+ def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil, authorization_check: nil, clock_tolerance_seconds: nil)
482
510
  max_token_age_seconds = 300 if max_token_age_seconds.nil?
511
+ clock_tolerance_seconds = 0 if clock_tolerance_seconds.nil?
483
512
 
484
513
  issuer = 'stytch.com/' + @project_id
485
514
  begin
486
515
  decoded_token = JWT.decode session_jwt, nil, true,
487
- { jwks: @jwks_loader, iss: issuer, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'] }
516
+ { jwks: @jwks_loader, iss: issuer, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'], nbf_leeway: clock_tolerance_seconds }
488
517
 
489
518
  session = decoded_token[0]
490
519
  iat_time = Time.at(session['iat']).to_datetime
@@ -58,7 +58,7 @@ module StytchB2B
58
58
  @saml = StytchB2B::SSO::SAML.new(@connection)
59
59
  end
60
60
 
61
- # Get all SSO Connections owned by the organization. /%}
61
+ # Get all SSO Connections owned by the organization.
62
62
  #
63
63
  # == Parameters:
64
64
  # organization_id::
@@ -96,7 +96,7 @@ module StytchB2B
96
96
  get_request(request, headers)
97
97
  end
98
98
 
99
- # Delete an existing SSO connection. /%}
99
+ # Delete an existing SSO connection.
100
100
  #
101
101
  # == Parameters:
102
102
  # organization_id::
@@ -300,7 +300,7 @@ module StytchB2B
300
300
  @connection = connection
301
301
  end
302
302
 
303
- # Create a new OIDC Connection. /%}
303
+ # Create a new OIDC Connection.
304
304
  #
305
305
  # == Parameters:
306
306
  # organization_id::
@@ -360,7 +360,6 @@ module StytchB2B
360
360
  # * `token_url`
361
361
  # * `userinfo_url`
362
362
  # * `jwks_url`
363
- # /%}
364
363
  #
365
364
  # == Parameters:
366
365
  # organization_id::
@@ -528,7 +527,7 @@ module StytchB2B
528
527
  @connection = connection
529
528
  end
530
529
 
531
- # Create a new SAML Connection. /%}
530
+ # Create a new SAML Connection.
532
531
  #
533
532
  # == Parameters:
534
533
  # organization_id::
@@ -577,7 +576,6 @@ module StytchB2B
577
576
  # * `attribute_mapping`
578
577
  # * `idp_entity_id`
579
578
  # * `x509_certificate`
580
- # /%}
581
579
  #
582
580
  # == Parameters:
583
581
  # organization_id::
@@ -670,7 +668,6 @@ module StytchB2B
670
668
  # * `idp_entity_id`
671
669
  # * `x509_certificate`
672
670
  # * `attribute_mapping` (must be supplied using [Update SAML Connection](update-saml-connection))
673
- # /%}
674
671
  #
675
672
  # == Parameters:
676
673
  # organization_id::
@@ -715,7 +712,6 @@ module StytchB2B
715
712
  # Delete a SAML verification certificate.
716
713
  #
717
714
  # You may need to do this when rotating certificates from your IdP, since Stytch allows a maximum of 5 certificates per connection. There must always be at least one certificate per active connection.
718
- # /%}
719
715
  #
720
716
  # == Parameters:
721
717
  # organization_id::
data/lib/stytch/client.rb CHANGED
@@ -22,11 +22,12 @@ module Stytch
22
22
  @api_host = api_host(env, project_id)
23
23
  @project_id = project_id
24
24
  @secret = secret
25
+ @is_b2b_client = false
25
26
 
26
27
  create_connection(&block)
27
28
 
28
29
  @crypto_wallets = Stytch::CryptoWallets.new(@connection)
29
- @m2m = Stytch::M2M.new(@connection, @project_id)
30
+ @m2m = Stytch::M2M.new(@connection, @project_id, @is_b2b_client)
30
31
  @magic_links = Stytch::MagicLinks.new(@connection)
31
32
  @oauth = Stytch::OAuth.new(@connection)
32
33
  @otps = Stytch::OTPs.new(@connection)
data/lib/stytch/errors.rb CHANGED
@@ -49,4 +49,11 @@ module Stytch
49
49
  super(msg)
50
50
  end
51
51
  end
52
+
53
+ class M2MPermissionError < StandardError
54
+ def initialize(has_scopes, required_scopes)
55
+ msg = "Missing at least one required scope from #{required_scopes} for M2M request with scopes #{has_scopes}"
56
+ super(msg)
57
+ end
58
+ end
52
59
  end
data/lib/stytch/m2m.rb CHANGED
@@ -13,12 +13,13 @@ module Stytch
13
13
  include Stytch::RequestHelper
14
14
  attr_reader :clients
15
15
 
16
- def initialize(connection, project_id)
16
+ def initialize(connection, project_id, is_b2b_client)
17
17
  @connection = connection
18
18
 
19
19
  @clients = Stytch::M2M::Clients.new(@connection)
20
20
  @project_id = project_id
21
21
  @cache_last_update = 0
22
+ @is_b2b_client = is_b2b_client
22
23
  @jwks_loader = lambda do |options|
23
24
  @cached_keys = nil if options[:invalidate] && @cache_last_update < Time.now.to_i - 300
24
25
  @cached_keys ||= begin
@@ -37,9 +38,11 @@ module Stytch
37
38
  def get_jwks(
38
39
  project_id:
39
40
  )
41
+ headers = {}
40
42
  query_params = {}
41
- request = request_with_query_params("/v1/sessions/jwks/#{project_id}", query_params)
42
- get_request(request)
43
+ path = @is_b2b_client ? "/v1/b2b/sessions/jwks/#{project_id}" : "/v1/sessions/jwks/#{project_id}"
44
+ request = request_with_query_params(path, query_params)
45
+ get_request(request, headers)
43
46
  end
44
47
  # ENDMANUAL(M2M::get_jwks)
45
48
 
@@ -96,6 +99,13 @@ module Stytch
96
99
  # max_token_age::
97
100
  # The maximum possible lifetime in seconds for the token to be valid.
98
101
  # The type of this field is nilable +Integer+.
102
+ # scope_authorization_func::
103
+ # A function to check if the token has the required scopes. This defaults to a function that assumes
104
+ # scopes are either direct string matches or written in the form "action:resource". See the
105
+ # documentation for +perform_authorization_check+ for more information.
106
+ # clock_tolerance_seconds:
107
+ # The tolerance to use during verification of the nbf claim. This can help with clock drift issues.
108
+ # The type of this field is nilable +Integer+.
99
109
  # == Returns:
100
110
  # +nil+ if the token could not be validated, or an object with the following fields:
101
111
  # scopes::
@@ -107,9 +117,15 @@ module Stytch
107
117
  # custom_claims::
108
118
  # A map of custom claims present in the token.
109
119
  # The type of this field is +object+.
110
- def authenticate_token(access_token:, required_scopes: nil, max_token_age: nil)
120
+ def authenticate_token(
121
+ access_token:,
122
+ required_scopes: nil,
123
+ max_token_age: nil,
124
+ scope_authorization_func: method(:perform_authorization_check),
125
+ clock_tolerance_seconds: nil
126
+ )
111
127
  # Intentionally allow this to re-raise if authentication fails
112
- decoded_jwt = authenticate_token_local(access_token)
128
+ decoded_jwt = authenticate_token_local(access_token, clock_tolerance_seconds: clock_tolerance_seconds)
113
129
 
114
130
  iat_time = Time.at(decoded_jwt['iat']).to_datetime
115
131
 
@@ -119,20 +135,58 @@ module Stytch
119
135
  resp = marshal_jwt_into_response(decoded_jwt)
120
136
 
121
137
  unless required_scopes.nil?
122
- for scope in required_scopes
123
- raise TokenMissingScopeError, scope unless resp['scopes'].include?(scope)
124
- end
138
+ is_authorized = scope_authorization_func.call(
139
+ has_scopes: resp['scopes'],
140
+ required_scopes: required_scopes
141
+ )
142
+ raise M2MPermissionError.new(resp['scopes'], required_scopes) unless is_authorized
125
143
  end
126
144
 
127
145
  resp
128
146
  end
129
147
 
148
+ # Performs an authorization check against an M2M client and a set of required
149
+ # scopes. Returns true if the client has all the required scopes, false otherwise.
150
+ # A scope can match if the client has a wildcard resource or the specific resource.
151
+ # This function assumes that scopes are of the form "action:resource" or just
152
+ # "specific_scope". It is _also_ possible to represent scopes as "resource:action",
153
+ # but it is ultimately up to the developer to ensure consistency in the scopes format.
154
+ # Note that a scope of "*" will only match another literal "*" because wildcards are
155
+ # *not* supported in the prefix piece of a scope.
156
+ def perform_authorization_check(
157
+ has_scopes:,
158
+ required_scopes:
159
+ )
160
+ client_scopes = Hash.new { |hash, key| hash[key] = Set.new }
161
+ has_scopes.each do |scope|
162
+ action = scope
163
+ resource = '-'
164
+ action, resource = scope.split(':') if scope.include?(':')
165
+ client_scopes[action].add(resource)
166
+ end
167
+
168
+ required_scopes.each do |required_scope|
169
+ required_action = required_scope
170
+ required_resource = '-'
171
+ required_action, required_resource = required_scope.split(':') if required_scope.include?(':')
172
+ return false unless client_scopes.key?(required_action)
173
+
174
+ resources = client_scopes[required_action]
175
+ # The client can either have a wildcard resource or the specific resource
176
+ return false unless resources.include?('*') || resources.include?(required_resource)
177
+ end
178
+
179
+ true
180
+ end
181
+
130
182
  # Parse a M2M token and verify the signature locally (without calling /authenticate in the API)
131
- def authenticate_token_local(jwt)
183
+ # If clock_tolerance_seconds is not supplied 0 seconds will be used as the default.
184
+ def authenticate_token_local(jwt, clock_tolerance_seconds: nil)
185
+ clock_tolerance_seconds = 0 if clock_tolerance_seconds.nil?
132
186
  issuer = 'stytch.com/' + @project_id
133
187
  begin
134
188
  decoded_token = JWT.decode jwt, nil, true,
135
- { jwks: @jwks_loader, iss: issuer, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'] }
189
+ { jwks: @jwks_loader, iss: issuer, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'], nbf_leeway: clock_tolerance_seconds }
136
190
  decoded_token[0]
137
191
  rescue JWT::InvalidIssuerError
138
192
  raise JWTInvalidIssuerError
@@ -34,7 +34,7 @@ module Stytch
34
34
  # The email address of the end user.
35
35
  # The type of this field is +String+.
36
36
  # password::
37
- # The password of the user
37
+ # The password for the user. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
38
38
  # The type of this field is +String+.
39
39
  # session_duration_minutes::
40
40
  # Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist,
@@ -127,7 +127,7 @@ module Stytch
127
127
  # The email address of the end user.
128
128
  # The type of this field is +String+.
129
129
  # password::
130
- # The password of the user
130
+ # The password for the user. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
131
131
  # The type of this field is +String+.
132
132
  # session_token::
133
133
  # The `session_token` associated with a User's existing Session.
@@ -214,7 +214,7 @@ module Stytch
214
214
  #
215
215
  # == Parameters:
216
216
  # password::
217
- # The password of the user
217
+ # The password for the user. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
218
218
  # The type of this field is +String+.
219
219
  # email::
220
220
  # The email address of the end user.
@@ -456,7 +456,7 @@ module Stytch
456
456
  # See examples and read more about redirect URLs [here](https://stytch.com/docs/guides/dashboard/redirect-urls).
457
457
  # The type of this field is +String+.
458
458
  # password::
459
- # The password of the user
459
+ # The password for the user. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
460
460
  # The type of this field is +String+.
461
461
  # session_token::
462
462
  # The `session_token` associated with a User's existing Session.
@@ -651,7 +651,7 @@ module Stytch
651
651
  #
652
652
  # == Parameters:
653
653
  # password::
654
- # The password of the user
654
+ # The password for the user. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
655
655
  # The type of this field is +String+.
656
656
  # session_token::
657
657
  # The `session_token` associated with a User's existing Session.
@@ -203,13 +203,16 @@ module Stytch
203
203
  # max_token_age_seconds seconds ago, then just verify locally and don't call the API
204
204
  # To force remote validation for all tokens, set max_token_age_seconds to 0 or call authenticate()
205
205
  # If max_token_age_seconds is not supplied 300 seconds will be used as the default.
206
+ # If clock_tolerance_seconds is not supplied 0 seconds will be used as the default.
206
207
  def authenticate_jwt(
207
208
  session_jwt,
208
209
  max_token_age_seconds: nil,
209
210
  session_duration_minutes: nil,
210
- session_custom_claims: nil
211
+ session_custom_claims: nil,
212
+ clock_tolerance_seconds: nil
211
213
  )
212
214
  max_token_age_seconds = 300 if max_token_age_seconds.nil?
215
+ clock_tolerance_seconds = 0 if clock_tolerance_seconds.nil?
213
216
 
214
217
  if max_token_age_seconds == 0
215
218
  return authenticate(
@@ -219,7 +222,11 @@ module Stytch
219
222
  )
220
223
  end
221
224
 
222
- session = authenticate_jwt_local(session_jwt, max_token_age_seconds: max_token_age_seconds)
225
+ session = authenticate_jwt_local(
226
+ session_jwt,
227
+ max_token_age_seconds: max_token_age_seconds,
228
+ clock_tolerance_seconds: clock_tolerance_seconds
229
+ )
223
230
  return session unless session.nil?
224
231
 
225
232
  authenticate(
@@ -241,13 +248,15 @@ module Stytch
241
248
  # function to get the JWK
242
249
  # This method never authenticates a JWT directly with the API
243
250
  # If max_token_age_seconds is not supplied 300 seconds will be used as the default.
244
- def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil)
251
+ # If clock_tolerance_seconds is not supplied 0 seconds will be used as the default.
252
+ def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil, clock_tolerance_seconds: nil)
245
253
  max_token_age_seconds = 300 if max_token_age_seconds.nil?
254
+ clock_tolerance_seconds = 0 if clock_tolerance_seconds.nil?
246
255
 
247
256
  issuer = 'stytch.com/' + @project_id
248
257
  begin
249
258
  decoded_token = JWT.decode session_jwt, nil, true,
250
- { jwks: @jwks_loader, iss: issuer, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'] }
259
+ { jwks: @jwks_loader, iss: issuer, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'], nbf_leeway: clock_tolerance_seconds }
251
260
 
252
261
  session = decoded_token[0]
253
262
  iat_time = Time.at(session['iat']).to_datetime
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stytch
4
- VERSION = '9.0.0'
4
+ VERSION = '9.2.0'
5
5
  end
data/stytch.gemspec CHANGED
@@ -30,6 +30,6 @@ Gem::Specification.new do |spec|
30
30
  spec.add_dependency 'jwt', '>= 2.3.0'
31
31
 
32
32
  spec.add_development_dependency 'rspec', '~> 3.11.0'
33
- spec.add_development_dependency 'rubocop', '1.56.3'
33
+ spec.add_development_dependency 'rubocop', '1.64.1'
34
34
  spec.add_development_dependency 'rubocop-rspec', '2.24.0'
35
35
  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: 9.0.0
4
+ version: 9.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - stytch
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-03 00:00:00.000000000 Z
11
+ date: 2024-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -78,14 +78,14 @@ dependencies:
78
78
  requirements:
79
79
  - - '='
80
80
  - !ruby/object:Gem::Version
81
- version: 1.56.3
81
+ version: 1.64.1
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - '='
87
87
  - !ruby/object:Gem::Version
88
- version: 1.56.3
88
+ version: 1.64.1
89
89
  - !ruby/object:Gem::Dependency
90
90
  name: rubocop-rspec
91
91
  requirement: !ruby/object:Gem::Requirement