stytch 6.6.0 → 7.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/stytch/b2b_client.rb +9 -3
- data/lib/stytch/b2b_discovery.rb +24 -11
- data/lib/stytch/b2b_magic_links.rb +23 -7
- data/lib/stytch/b2b_oauth.rb +12 -2
- data/lib/stytch/b2b_organizations.rb +259 -40
- data/lib/stytch/b2b_otp.rb +4 -2
- data/lib/stytch/b2b_passwords.rb +34 -8
- data/lib/stytch/b2b_rbac.rb +48 -0
- data/lib/stytch/b2b_sessions.rb +106 -38
- data/lib/stytch/b2b_sso.rb +127 -21
- data/lib/stytch/client.rb +2 -2
- data/lib/stytch/crypto_wallets.rb +4 -2
- data/lib/stytch/errors.rb +14 -0
- data/lib/stytch/m2m.rb +16 -9
- data/lib/stytch/magic_links.rb +12 -6
- data/lib/stytch/method_options.rb +22 -0
- data/lib/stytch/oauth.rb +4 -2
- data/lib/stytch/otps.rb +14 -7
- data/lib/stytch/passwords.rb +16 -8
- data/lib/stytch/rbac_local.rb +58 -0
- data/lib/stytch/request_helper.rb +12 -8
- data/lib/stytch/sessions.rb +24 -11
- data/lib/stytch/totps.rb +8 -4
- data/lib/stytch/users.rb +29 -15
- data/lib/stytch/version.rb +1 -1
- data/lib/stytch/webauthn.rb +39 -24
- metadata +5 -2
data/lib/stytch/b2b_otp.rb
CHANGED
@@ -84,6 +84,7 @@ module StytchB2B
|
|
84
84
|
mfa_phone_number: nil,
|
85
85
|
locale: nil
|
86
86
|
)
|
87
|
+
headers = {}
|
87
88
|
request = {
|
88
89
|
organization_id: organization_id,
|
89
90
|
member_id: member_id
|
@@ -91,7 +92,7 @@ module StytchB2B
|
|
91
92
|
request[:mfa_phone_number] = mfa_phone_number unless mfa_phone_number.nil?
|
92
93
|
request[:locale] = locale unless locale.nil?
|
93
94
|
|
94
|
-
post_request('/v1/b2b/otps/sms/send', request)
|
95
|
+
post_request('/v1/b2b/otps/sms/send', request, headers)
|
95
96
|
end
|
96
97
|
|
97
98
|
# SMS OTPs may not be used as a primary authentication mechanism. They can be used to complete an MFA requirement, or they can be used as a step-up factor to be added to an existing session.
|
@@ -197,6 +198,7 @@ module StytchB2B
|
|
197
198
|
session_custom_claims: nil,
|
198
199
|
set_mfa_enrollment: nil
|
199
200
|
)
|
201
|
+
headers = {}
|
200
202
|
request = {
|
201
203
|
organization_id: organization_id,
|
202
204
|
member_id: member_id,
|
@@ -209,7 +211,7 @@ module StytchB2B
|
|
209
211
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
210
212
|
request[:set_mfa_enrollment] = set_mfa_enrollment unless set_mfa_enrollment.nil?
|
211
213
|
|
212
|
-
post_request('/v1/b2b/otps/sms/authenticate', request)
|
214
|
+
post_request('/v1/b2b/otps/sms/authenticate', request, headers)
|
213
215
|
end
|
214
216
|
end
|
215
217
|
end
|
data/lib/stytch/b2b_passwords.rb
CHANGED
@@ -78,12 +78,13 @@ module StytchB2B
|
|
78
78
|
password:,
|
79
79
|
email_address: nil
|
80
80
|
)
|
81
|
+
headers = {}
|
81
82
|
request = {
|
82
83
|
password: password
|
83
84
|
}
|
84
85
|
request[:email_address] = email_address unless email_address.nil?
|
85
86
|
|
86
|
-
post_request('/v1/b2b/passwords/strength_check', request)
|
87
|
+
post_request('/v1/b2b/passwords/strength_check', request, headers)
|
87
88
|
end
|
88
89
|
|
89
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.
|
@@ -127,6 +128,21 @@ module StytchB2B
|
|
127
128
|
# frontend SDK, and should not be used to store critical information. See the [Metadata resource](https://stytch.com/docs/b2b/api/metadata)
|
128
129
|
# for complete field behavior details.
|
129
130
|
# The type of this field is nilable +object+.
|
131
|
+
# roles::
|
132
|
+
# (Coming Soon) Roles to explicitly assign to this Member.
|
133
|
+
# Will completely replace any existing explicitly assigned roles. See the
|
134
|
+
# [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment.
|
135
|
+
#
|
136
|
+
# If a Role is removed from a Member, and the Member is also implicitly assigned this Role from an SSO connection
|
137
|
+
# or an SSO group, we will by default revoke any existing sessions for the Member that contain any SSO
|
138
|
+
# authentication factors with the affected connection ID. You can preserve these sessions by passing in the
|
139
|
+
# `preserve_existing_sessions` parameter with a value of `true`.
|
140
|
+
# The type of this field is nilable list of +String+.
|
141
|
+
# preserve_existing_sessions::
|
142
|
+
# (Coming Soon) Whether to preserve existing sessions when explicit Roles that are revoked are also implicitly assigned
|
143
|
+
# by SSO connection or SSO group. Defaults to `false` - that is, existing Member Sessions that contain SSO
|
144
|
+
# authentication factors with the affected SSO connection IDs will be revoked.
|
145
|
+
# The type of this field is nilable +Boolean+.
|
130
146
|
#
|
131
147
|
# == Returns:
|
132
148
|
# An object with the following fields:
|
@@ -160,8 +176,11 @@ module StytchB2B
|
|
160
176
|
pbkdf_2_config: nil,
|
161
177
|
name: nil,
|
162
178
|
trusted_metadata: nil,
|
163
|
-
untrusted_metadata: nil
|
179
|
+
untrusted_metadata: nil,
|
180
|
+
roles: nil,
|
181
|
+
preserve_existing_sessions: nil
|
164
182
|
)
|
183
|
+
headers = {}
|
165
184
|
request = {
|
166
185
|
email_address: email_address,
|
167
186
|
hash: hash,
|
@@ -176,8 +195,10 @@ module StytchB2B
|
|
176
195
|
request[:name] = name unless name.nil?
|
177
196
|
request[:trusted_metadata] = trusted_metadata unless trusted_metadata.nil?
|
178
197
|
request[:untrusted_metadata] = untrusted_metadata unless untrusted_metadata.nil?
|
198
|
+
request[:roles] = roles unless roles.nil?
|
199
|
+
request[:preserve_existing_sessions] = preserve_existing_sessions unless preserve_existing_sessions.nil?
|
179
200
|
|
180
|
-
post_request('/v1/b2b/passwords/migrate', request)
|
201
|
+
post_request('/v1/b2b/passwords/migrate', request, headers)
|
181
202
|
end
|
182
203
|
|
183
204
|
# Authenticate a member with their email address and password. This endpoint verifies that the member has a password currently set, and that the entered password is correct.
|
@@ -285,6 +306,7 @@ module StytchB2B
|
|
285
306
|
session_custom_claims: nil,
|
286
307
|
locale: nil
|
287
308
|
)
|
309
|
+
headers = {}
|
288
310
|
request = {
|
289
311
|
organization_id: organization_id,
|
290
312
|
email_address: email_address,
|
@@ -296,7 +318,7 @@ module StytchB2B
|
|
296
318
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
297
319
|
request[:locale] = locale unless locale.nil?
|
298
320
|
|
299
|
-
post_request('/v1/b2b/passwords/authenticate', request)
|
321
|
+
post_request('/v1/b2b/passwords/authenticate', request, headers)
|
300
322
|
end
|
301
323
|
|
302
324
|
class Email
|
@@ -374,6 +396,7 @@ module StytchB2B
|
|
374
396
|
locale: nil,
|
375
397
|
reset_password_template_id: nil
|
376
398
|
)
|
399
|
+
headers = {}
|
377
400
|
request = {
|
378
401
|
organization_id: organization_id,
|
379
402
|
email_address: email_address
|
@@ -385,7 +408,7 @@ module StytchB2B
|
|
385
408
|
request[:locale] = locale unless locale.nil?
|
386
409
|
request[:reset_password_template_id] = reset_password_template_id unless reset_password_template_id.nil?
|
387
410
|
|
388
|
-
post_request('/v1/b2b/passwords/email/reset/start', request)
|
411
|
+
post_request('/v1/b2b/passwords/email/reset/start', request, headers)
|
389
412
|
end
|
390
413
|
|
391
414
|
# Reset the member's password and authenticate them. This endpoint checks that the password reset token is valid, hasn’t expired, or already been used.
|
@@ -500,6 +523,7 @@ module StytchB2B
|
|
500
523
|
session_custom_claims: nil,
|
501
524
|
locale: nil
|
502
525
|
)
|
526
|
+
headers = {}
|
503
527
|
request = {
|
504
528
|
password_reset_token: password_reset_token,
|
505
529
|
password: password
|
@@ -511,7 +535,7 @@ module StytchB2B
|
|
511
535
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
512
536
|
request[:locale] = locale unless locale.nil?
|
513
537
|
|
514
|
-
post_request('/v1/b2b/passwords/email/reset', request)
|
538
|
+
post_request('/v1/b2b/passwords/email/reset', request, headers)
|
515
539
|
end
|
516
540
|
end
|
517
541
|
|
@@ -611,6 +635,7 @@ module StytchB2B
|
|
611
635
|
session_custom_claims: nil,
|
612
636
|
locale: nil
|
613
637
|
)
|
638
|
+
headers = {}
|
614
639
|
request = {
|
615
640
|
organization_id: organization_id,
|
616
641
|
password: password
|
@@ -621,7 +646,7 @@ module StytchB2B
|
|
621
646
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
622
647
|
request[:locale] = locale unless locale.nil?
|
623
648
|
|
624
|
-
post_request('/v1/b2b/passwords/session/reset', request)
|
649
|
+
post_request('/v1/b2b/passwords/session/reset', request, headers)
|
625
650
|
end
|
626
651
|
end
|
627
652
|
|
@@ -742,6 +767,7 @@ module StytchB2B
|
|
742
767
|
session_custom_claims: nil,
|
743
768
|
locale: nil
|
744
769
|
)
|
770
|
+
headers = {}
|
745
771
|
request = {
|
746
772
|
email_address: email_address,
|
747
773
|
existing_password: existing_password,
|
@@ -754,7 +780,7 @@ module StytchB2B
|
|
754
780
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
755
781
|
request[:locale] = locale unless locale.nil?
|
756
782
|
|
757
|
-
post_request('/v1/b2b/passwords/existing_password/reset', request)
|
783
|
+
post_request('/v1/b2b/passwords/existing_password/reset', request, headers)
|
758
784
|
end
|
759
785
|
end
|
760
786
|
end
|
@@ -0,0 +1,48 @@
|
|
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 StytchB2B
|
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 automatically be loaded and refreshed in the background to allow for local evaluations, eliminating the need for an extra request to Stytch.
|
22
|
+
#
|
23
|
+
# Resources and Roles can be created and managed within the [Dashboard](/dashboard). Additionally, [Role assignment](https://stytch.com/docs/b2b/guides/rbac/role-assignment) can be programmatically managed through certain Stytch API endpoints.
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# Check out the [RBAC overview](https://stytch.com/docs/b2b/guides/rbac/overview) to learn more about Stytch's RBAC permissioning model or [contact us](https://share.hsforms.com/1qkU__-1CT1--lnqRDxphXgd4bkb) to request early access.
|
27
|
+
#
|
28
|
+
# == Parameters:
|
29
|
+
#
|
30
|
+
# == Returns:
|
31
|
+
# An object with the following fields:
|
32
|
+
# request_id::
|
33
|
+
# 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.
|
34
|
+
# The type of this field is +String+.
|
35
|
+
# status_code::
|
36
|
+
# 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.
|
37
|
+
# The type of this field is +Integer+.
|
38
|
+
# policy::
|
39
|
+
# The RBAC Policy document that contains all defined Roles and Resources – which are managed in the [Dashboard](/dashboard). Read more about these entities and how they work in our [RBAC overview](https://stytch.com/docs/b2b/guides/rbac/overview).
|
40
|
+
# The type of this field is nilable +Policy+ (+object+).
|
41
|
+
def policy
|
42
|
+
headers = {}
|
43
|
+
query_params = {}
|
44
|
+
request = request_with_query_params('/v1/b2b/rbac/policy', query_params)
|
45
|
+
get_request(request, headers)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/stytch/b2b_sessions.rb
CHANGED
@@ -15,9 +15,10 @@ module StytchB2B
|
|
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|
|
@@ -58,18 +59,26 @@ module StytchB2B
|
|
58
59
|
organization_id:,
|
59
60
|
member_id:
|
60
61
|
)
|
62
|
+
headers = {}
|
61
63
|
query_params = {
|
62
64
|
organization_id: organization_id,
|
63
65
|
member_id: member_id
|
64
66
|
}
|
65
67
|
request = request_with_query_params('/v1/b2b/sessions', query_params)
|
66
|
-
get_request(request)
|
68
|
+
get_request(request, headers)
|
67
69
|
end
|
68
70
|
|
69
71
|
# 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.
|
70
72
|
#
|
71
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.
|
72
74
|
#
|
75
|
+
# 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://github.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
|
76
|
+
# In addition, the `organization_id` passed in the authorization check must match the Member's Organization.
|
77
|
+
#
|
78
|
+
# If the Member is not authorized to perform the specified action on the specified Resource, or if the
|
79
|
+
# `organization_id` does not match the Member's Organization, a 403 error will be thrown.
|
80
|
+
# Otherwise, the response will contain a list of Roles that satisfied the authorization check.
|
81
|
+
#
|
73
82
|
# == Parameters:
|
74
83
|
# session_token::
|
75
84
|
# A secret token for a given Stytch Session.
|
@@ -95,6 +104,21 @@ module StytchB2B
|
|
95
104
|
# delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored.
|
96
105
|
# Total custom claims size cannot exceed four kilobytes.
|
97
106
|
# The type of this field is nilable +object+.
|
107
|
+
# authorization_check::
|
108
|
+
# (Coming Soon) If an `authorization_check` object is passed in, this endpoint will also check if the Member is
|
109
|
+
# authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if
|
110
|
+
# their Member Session contains a Role, assigned
|
111
|
+
# [explicitly or implicitly](https://github.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
|
112
|
+
# In addition, the `organization_id` passed in the authorization check must match the Member's Organization.
|
113
|
+
#
|
114
|
+
# The Roles on the Member Session may differ from the Roles you see on the Member object - Roles that are implicitly
|
115
|
+
# assigned by SSO connection or SSO group will only be valid for a Member Session if there is at least one authentication
|
116
|
+
# factor on the Member Session from the specified SSO connection.
|
117
|
+
#
|
118
|
+
# If the Member is not authorized to perform the specified action on the specified Resource, or if the
|
119
|
+
# `organization_id` does not match the Member's Organization, a 403 error will be thrown.
|
120
|
+
# Otherwise, the response will contain a list of Roles that satisfied the authorization check.
|
121
|
+
# The type of this field is nilable +AuthorizationCheck+ (+object+).
|
98
122
|
#
|
99
123
|
# == Returns:
|
100
124
|
# An object with the following fields:
|
@@ -119,19 +143,26 @@ module StytchB2B
|
|
119
143
|
# status_code::
|
120
144
|
# 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.
|
121
145
|
# The type of this field is +Integer+.
|
146
|
+
# verdict::
|
147
|
+
# (Coming Soon) If an `authorization_check` is provided in the request and the check succeeds, this field will return
|
148
|
+
# the complete list of Roles that gave the Member permission to perform the specified action on the specified Resource.
|
149
|
+
# The type of this field is nilable +AuthorizationVerdict+ (+object+).
|
122
150
|
def authenticate(
|
123
151
|
session_token: nil,
|
124
152
|
session_duration_minutes: nil,
|
125
153
|
session_jwt: nil,
|
126
|
-
session_custom_claims: nil
|
154
|
+
session_custom_claims: nil,
|
155
|
+
authorization_check: nil
|
127
156
|
)
|
157
|
+
headers = {}
|
128
158
|
request = {}
|
129
159
|
request[:session_token] = session_token unless session_token.nil?
|
130
160
|
request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
|
131
161
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
132
162
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
163
|
+
request[:authorization_check] = authorization_check unless authorization_check.nil?
|
133
164
|
|
134
|
-
post_request('/v1/b2b/sessions/authenticate', request)
|
165
|
+
post_request('/v1/b2b/sessions/authenticate', request, headers)
|
135
166
|
end
|
136
167
|
|
137
168
|
# Revoke a Session and immediately invalidate all its tokens. To revoke a specific Session, pass either the `member_session_id`, `session_token`, or `session_jwt`. To revoke all Sessions for a Member, pass the `member_id`.
|
@@ -164,13 +195,14 @@ module StytchB2B
|
|
164
195
|
session_jwt: nil,
|
165
196
|
member_id: nil
|
166
197
|
)
|
198
|
+
headers = {}
|
167
199
|
request = {}
|
168
200
|
request[:member_session_id] = member_session_id unless member_session_id.nil?
|
169
201
|
request[:session_token] = session_token unless session_token.nil?
|
170
202
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
171
203
|
request[:member_id] = member_id unless member_id.nil?
|
172
204
|
|
173
|
-
post_request('/v1/b2b/sessions/revoke', request)
|
205
|
+
post_request('/v1/b2b/sessions/revoke', request, headers)
|
174
206
|
end
|
175
207
|
|
176
208
|
# Use this endpoint to exchange a Member's existing session for another session in a different Organization. This can be used to accept an invite, but not to create a new member via domain matching.
|
@@ -270,6 +302,7 @@ module StytchB2B
|
|
270
302
|
session_custom_claims: nil,
|
271
303
|
locale: nil
|
272
304
|
)
|
305
|
+
headers = {}
|
273
306
|
request = {
|
274
307
|
organization_id: organization_id
|
275
308
|
}
|
@@ -279,7 +312,7 @@ module StytchB2B
|
|
279
312
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
280
313
|
request[:locale] = locale unless locale.nil?
|
281
314
|
|
282
|
-
post_request('/v1/b2b/sessions/exchange', request)
|
315
|
+
post_request('/v1/b2b/sessions/exchange', request, headers)
|
283
316
|
end
|
284
317
|
|
285
318
|
# Get the JSON Web Key Set (JWKS) for a project.
|
@@ -311,9 +344,10 @@ module StytchB2B
|
|
311
344
|
def get_jwks(
|
312
345
|
project_id:
|
313
346
|
)
|
347
|
+
headers = {}
|
314
348
|
query_params = {}
|
315
349
|
request = request_with_query_params("/v1/b2b/sessions/jwks/#{project_id}", query_params)
|
316
|
-
get_request(request)
|
350
|
+
get_request(request, headers)
|
317
351
|
end
|
318
352
|
|
319
353
|
# MANUAL(Sessions::authenticate_jwt)(SERVICE_METHOD)
|
@@ -325,38 +359,43 @@ module StytchB2B
|
|
325
359
|
# If max_token_age_seconds is set and the JWT was issued (based on the "iat" claim) less than
|
326
360
|
# max_token_age_seconds seconds ago, then just verify locally and don't call the API
|
327
361
|
# To force remote validation for all tokens, set max_token_age_seconds to 0 or call authenticate()
|
362
|
+
# Note that the 'user_id' field of the returned session is DEPRECATED: Use member_id instead
|
363
|
+
# This field will be removed in a future MAJOR release.
|
364
|
+
# If max_token_age_seconds is not supplied 300 seconds will be used as the default.
|
328
365
|
def authenticate_jwt(
|
329
366
|
session_jwt,
|
330
367
|
max_token_age_seconds: nil,
|
331
368
|
session_duration_minutes: nil,
|
332
|
-
session_custom_claims: nil
|
369
|
+
session_custom_claims: nil,
|
370
|
+
authorization_check: nil
|
333
371
|
)
|
372
|
+
max_token_age_seconds = 300 if max_token_age_seconds.nil?
|
373
|
+
|
334
374
|
if max_token_age_seconds == 0
|
335
375
|
return authenticate(
|
336
376
|
session_jwt: session_jwt,
|
337
377
|
session_duration_minutes: session_duration_minutes,
|
338
|
-
session_custom_claims: session_custom_claims
|
378
|
+
session_custom_claims: session_custom_claims,
|
379
|
+
authorization_check: authorization_check
|
339
380
|
)
|
340
381
|
end
|
341
382
|
|
342
|
-
decoded_jwt = authenticate_jwt_local(session_jwt)
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
session_custom_claims: session_custom_claims
|
352
|
-
)
|
353
|
-
end
|
383
|
+
decoded_jwt = authenticate_jwt_local(session_jwt: session_jwt, authorization_check: authorization_check)
|
384
|
+
return decoded_jwt unless decoded_jwt.nil?
|
385
|
+
|
386
|
+
authenticate(
|
387
|
+
session_jwt: session_jwt,
|
388
|
+
session_duration_minutes: session_duration_minutes,
|
389
|
+
session_custom_claims: session_custom_claims,
|
390
|
+
authorization_check: authorization_check
|
391
|
+
)
|
354
392
|
rescue StandardError
|
355
393
|
# JWT could not be verified locally. Check with the Stytch API.
|
356
394
|
authenticate(
|
357
395
|
session_jwt: session_jwt,
|
358
396
|
session_duration_minutes: session_duration_minutes,
|
359
|
-
session_custom_claims: session_custom_claims
|
397
|
+
session_custom_claims: session_custom_claims,
|
398
|
+
authorization_check: authorization_check
|
360
399
|
)
|
361
400
|
end
|
362
401
|
|
@@ -364,41 +403,70 @@ module StytchB2B
|
|
364
403
|
# Uses the cached value to get the JWK but if it is unavailable, it calls the get_jwks()
|
365
404
|
# function to get the JWK
|
366
405
|
# This method never authenticates a JWT directly with the API
|
367
|
-
|
406
|
+
# If max_token_age_seconds is not supplied 300 seconds will be used as the default.
|
407
|
+
def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil, authorization_check: nil)
|
408
|
+
max_token_age_seconds = 300 if max_token_age_seconds.nil?
|
409
|
+
|
368
410
|
issuer = 'stytch.com/' + @project_id
|
369
411
|
begin
|
370
412
|
decoded_token = JWT.decode session_jwt, nil, true,
|
371
413
|
{ jwks: @jwks_loader, iss: issuer, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'] }
|
372
|
-
|
414
|
+
|
415
|
+
session = decoded_token[0]
|
416
|
+
iat_time = Time.at(session['iat']).to_datetime
|
417
|
+
return nil unless iat_time + max_token_age_seconds >= Time.now
|
418
|
+
|
419
|
+
session = marshal_jwt_into_session(session)
|
373
420
|
rescue JWT::InvalidIssuerError
|
374
|
-
raise JWTInvalidIssuerError
|
421
|
+
raise Stytch::JWTInvalidIssuerError
|
375
422
|
rescue JWT::InvalidAudError
|
376
|
-
raise JWTInvalidAudienceError
|
423
|
+
raise Stytch::JWTInvalidAudienceError
|
377
424
|
rescue JWT::ExpiredSignature
|
378
|
-
raise JWTExpiredSignatureError
|
425
|
+
raise Stytch::JWTExpiredSignatureError
|
379
426
|
rescue JWT::IncorrectAlgorithm
|
380
|
-
raise JWTIncorrectAlgorithmError
|
427
|
+
raise Stytch::JWTIncorrectAlgorithmError
|
428
|
+
end
|
429
|
+
|
430
|
+
# Do the auth check - intentionally don't rescue errors from here
|
431
|
+
if authorization_check && session['roles']
|
432
|
+
@policy_cache.perform_authorization_check(
|
433
|
+
subject_roles: session['roles'],
|
434
|
+
subject_org_id: session['member_session']['organization_id'],
|
435
|
+
authorization_check: authorization_check
|
436
|
+
)
|
381
437
|
end
|
438
|
+
|
439
|
+
session
|
382
440
|
end
|
383
441
|
|
442
|
+
# Note that the 'user_id' field is DEPRECATED: Use member_id instead
|
443
|
+
# This field will be removed in a future MAJOR release.
|
384
444
|
def marshal_jwt_into_session(jwt)
|
385
445
|
stytch_claim = 'https://stytch.com/session'
|
446
|
+
organization_claim = 'https://stytch.com/organization'
|
447
|
+
|
386
448
|
expires_at = jwt[stytch_claim]['expires_at'] || Time.at(jwt['exp']).to_datetime.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
387
449
|
# The custom claim set is all the claims in the payload except for the standard claims and
|
388
450
|
# the Stytch session claim. The cleanest way to collect those seems to be naming what we want
|
389
451
|
# to omit and filtering the rest to collect the custom claims.
|
390
|
-
reserved_claims = ['aud', 'exp', 'iat', 'iss', 'jti', 'nbf', 'sub', stytch_claim]
|
452
|
+
reserved_claims = ['aud', 'exp', 'iat', 'iss', 'jti', 'nbf', 'sub', stytch_claim, organization_claim]
|
391
453
|
custom_claims = jwt.reject { |key, _| reserved_claims.include?(key) }
|
392
454
|
{
|
393
|
-
'
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
455
|
+
'member_session' => {
|
456
|
+
'session_id' => jwt[stytch_claim]['id'],
|
457
|
+
'organization_id' => jwt[organization_claim]['organization_id'],
|
458
|
+
'member_id' => jwt['sub'],
|
459
|
+
# DEPRECATED: Use member_id instead
|
460
|
+
'user_id' => jwt['sub'],
|
461
|
+
'started_at' => jwt[stytch_claim]['started_at'],
|
462
|
+
'last_accessed_at' => jwt[stytch_claim]['last_accessed_at'],
|
463
|
+
# For JWTs that include it, prefer the inner expires_at claim.
|
464
|
+
'expires_at' => expires_at,
|
465
|
+
'attributes' => jwt[stytch_claim]['attributes'],
|
466
|
+
'authentication_factors' => jwt[stytch_claim]['authentication_factors'],
|
467
|
+
'custom_claims' => custom_claims
|
468
|
+
},
|
469
|
+
'roles' => jwt[stytch_claim]['roles']
|
402
470
|
}
|
403
471
|
end
|
404
472
|
# ENDMANUAL(Sessions::authenticate_jwt)
|