stytch 6.4.0 → 7.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +13 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +21 -0
- data/DEVELOPMENT.md +5 -2
- data/README.md +1 -1
- data/lib/stytch/b2b_client.rb +13 -3
- data/lib/stytch/b2b_discovery.rb +41 -11
- data/lib/stytch/b2b_magic_links.rb +23 -7
- data/lib/stytch/b2b_oauth.rb +12 -2
- data/lib/stytch/b2b_organizations.rb +348 -47
- data/lib/stytch/b2b_otp.rb +35 -4
- data/lib/stytch/b2b_passwords.rb +92 -19
- data/lib/stytch/b2b_rbac.rb +47 -0
- data/lib/stytch/b2b_recovery_codes.rb +199 -0
- data/lib/stytch/b2b_sessions.rb +187 -7
- data/lib/stytch/b2b_sso.rb +172 -19
- data/lib/stytch/b2b_totps.rb +261 -0
- 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 +20 -12
- data/lib/stytch/method_options.rb +22 -0
- data/lib/stytch/oauth.rb +10 -4
- data/lib/stytch/otps.rb +26 -16
- data/lib/stytch/passwords.rb +62 -14
- data/lib/stytch/rbac_local.rb +58 -0
- data/lib/stytch/request_helper.rb +12 -8
- data/lib/stytch/sessions.rb +51 -28
- data/lib/stytch/totps.rb +9 -5
- data/lib/stytch/users.rb +30 -16
- data/lib/stytch/version.rb +1 -1
- data/lib/stytch/webauthn.rb +126 -24
- data/stytch.gemspec +2 -0
- metadata +36 -2
data/lib/stytch/otps.rb
CHANGED
@@ -101,6 +101,7 @@ module Stytch
|
|
101
101
|
session_jwt: nil,
|
102
102
|
session_custom_claims: nil
|
103
103
|
)
|
104
|
+
headers = {}
|
104
105
|
request = {
|
105
106
|
method_id: method_id,
|
106
107
|
code: code
|
@@ -112,7 +113,7 @@ module Stytch
|
|
112
113
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
113
114
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
114
115
|
|
115
|
-
post_request('/v1/otps/authenticate', request)
|
116
|
+
post_request('/v1/otps/authenticate', request, headers)
|
116
117
|
end
|
117
118
|
|
118
119
|
class Sms
|
@@ -129,11 +130,13 @@ module Stytch
|
|
129
130
|
# ### Cost to send SMS OTP
|
130
131
|
# Before configuring SMS or WhatsApp OTPs, please review how Stytch [bills the costs of international OTPs](https://stytch.com/pricing) and understand how to protect your app against [toll fraud](https://stytch.com/docs/guides/passcodes/toll-fraud/overview).
|
131
132
|
#
|
132
|
-
#
|
133
|
+
# __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Enable%20international%20SMS).
|
134
|
+
#
|
135
|
+
# Even when international SMS is enabled, we do not support sending SMS to countries on our [Unsupported countries list](https://stytch.com/docs/guides/passcodes/unsupported-countries).
|
133
136
|
#
|
134
|
-
#
|
137
|
+
# ### Add a phone number to an existing user
|
135
138
|
#
|
136
|
-
#
|
139
|
+
# This endpoint also allows you to add a new phone number to an existing Stytch User. Including a `user_id`, `session_token`, or `session_jwt` in your Send one-time passcode by SMS request will add the new, unverified phone number to the existing Stytch User. If the user successfully authenticates within 5 minutes, the new phone number will be marked as verified and remain permanently on the existing Stytch User. Otherwise, it will be removed from the User object, and any subsequent login requests using that phone number will create a new User.
|
137
140
|
#
|
138
141
|
# ### Next steps
|
139
142
|
#
|
@@ -190,6 +193,7 @@ module Stytch
|
|
190
193
|
session_token: nil,
|
191
194
|
session_jwt: nil
|
192
195
|
)
|
196
|
+
headers = {}
|
193
197
|
request = {
|
194
198
|
phone_number: phone_number
|
195
199
|
}
|
@@ -200,13 +204,18 @@ module Stytch
|
|
200
204
|
request[:session_token] = session_token unless session_token.nil?
|
201
205
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
202
206
|
|
203
|
-
post_request('/v1/otps/sms/send', request)
|
207
|
+
post_request('/v1/otps/sms/send', request, headers)
|
204
208
|
end
|
205
209
|
|
206
210
|
# Send a One-Time Passcode (OTP) to a User using their phone number. If the phone number is not associated with a user already, a user will be created.
|
207
211
|
#
|
208
212
|
# ### Cost to send SMS OTP
|
209
213
|
# Before configuring SMS or WhatsApp OTPs, please review how Stytch [bills the costs of international OTPs](https://stytch.com/pricing) and understand how to protect your app against [toll fraud](https://stytch.com/docs/guides/passcodes/toll-fraud/overview).
|
214
|
+
#
|
215
|
+
# __Note:__ SMS to phone numbers outside of the US and Canada is disabled by default for customers who did not use SMS prior to October 2023. If you're interested in sending international SMS, please reach out to [support@stytch.com](mailto:support@stytch.com?subject=Enable%20international%20SMS).
|
216
|
+
#
|
217
|
+
# Even when international SMS is enabled, we do not support sending SMS to countries on our [Unsupported countries list](https://stytch.com/docs/guides/passcodes/unsupported-countries).
|
218
|
+
#
|
210
219
|
# ### Next steps
|
211
220
|
#
|
212
221
|
# Collect the OTP which was delivered to the User. Call [Authenticate OTP](https://stytch.com/docs/api/authenticate-otp) using the OTP `code` along with the `phone_id` found in the response as the `method_id`.
|
@@ -261,6 +270,7 @@ module Stytch
|
|
261
270
|
create_user_as_pending: nil,
|
262
271
|
locale: nil
|
263
272
|
)
|
273
|
+
headers = {}
|
264
274
|
request = {
|
265
275
|
phone_number: phone_number
|
266
276
|
}
|
@@ -269,7 +279,7 @@ module Stytch
|
|
269
279
|
request[:create_user_as_pending] = create_user_as_pending unless create_user_as_pending.nil?
|
270
280
|
request[:locale] = locale unless locale.nil?
|
271
281
|
|
272
|
-
post_request('/v1/otps/sms/login_or_create', request)
|
282
|
+
post_request('/v1/otps/sms/login_or_create', request, headers)
|
273
283
|
end
|
274
284
|
end
|
275
285
|
|
@@ -289,9 +299,7 @@ module Stytch
|
|
289
299
|
#
|
290
300
|
# ### Add a phone number to an existing user
|
291
301
|
#
|
292
|
-
# This endpoint also allows you to add a new phone number to an existing Stytch User. Including a `user_id`, `session_token`, or `session_jwt` in
|
293
|
-
#
|
294
|
-
# Adding a new phone number to an existing Stytch User requires the user to be present and validate the phone number via OTP. This requirement is in place to prevent account takeover attacks.
|
302
|
+
# This endpoint also allows you to add a new phone number to an existing Stytch User. Including a `user_id`, `session_token`, or `session_jwt` in your Send one-time passcode by WhatsApp request will add the new, unverified phone number to the existing Stytch User. If the user successfully authenticates within 5 minutes, the new phone number will be marked as verified and remain permanently on the existing Stytch User. Otherwise, it will be removed from the User object, and any subsequent login requests using that phone number will create a new User.
|
295
303
|
#
|
296
304
|
# ### Next steps
|
297
305
|
#
|
@@ -348,6 +356,7 @@ module Stytch
|
|
348
356
|
session_token: nil,
|
349
357
|
session_jwt: nil
|
350
358
|
)
|
359
|
+
headers = {}
|
351
360
|
request = {
|
352
361
|
phone_number: phone_number
|
353
362
|
}
|
@@ -358,7 +367,7 @@ module Stytch
|
|
358
367
|
request[:session_token] = session_token unless session_token.nil?
|
359
368
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
360
369
|
|
361
|
-
post_request('/v1/otps/whatsapp/send', request)
|
370
|
+
post_request('/v1/otps/whatsapp/send', request, headers)
|
362
371
|
end
|
363
372
|
|
364
373
|
# Send a one-time passcode (OTP) to a User's WhatsApp using their phone number. If the phone number is not associated with a User already, a User will be created.
|
@@ -420,6 +429,7 @@ module Stytch
|
|
420
429
|
create_user_as_pending: nil,
|
421
430
|
locale: nil
|
422
431
|
)
|
432
|
+
headers = {}
|
423
433
|
request = {
|
424
434
|
phone_number: phone_number
|
425
435
|
}
|
@@ -428,7 +438,7 @@ module Stytch
|
|
428
438
|
request[:create_user_as_pending] = create_user_as_pending unless create_user_as_pending.nil?
|
429
439
|
request[:locale] = locale unless locale.nil?
|
430
440
|
|
431
|
-
post_request('/v1/otps/whatsapp/login_or_create', request)
|
441
|
+
post_request('/v1/otps/whatsapp/login_or_create', request, headers)
|
432
442
|
end
|
433
443
|
end
|
434
444
|
|
@@ -442,9 +452,7 @@ module Stytch
|
|
442
452
|
# Send a One-Time Passcode (OTP) to a User using their email. If you'd like to create a user and send them a passcode with one request, use our [log in or create endpoint](https://stytch.com/docs/api/log-in-or-create-user-by-email-otp).
|
443
453
|
#
|
444
454
|
# ### Add an email to an existing user
|
445
|
-
# This endpoint also allows you to add a new email to an existing Stytch User. Including a `user_id`, `session_token`, or `session_jwt` in
|
446
|
-
#
|
447
|
-
# Adding a new email to an existing Stytch User requires the User to be present and validate the email via OTP. This requirement is in place to prevent account takeover attacks.
|
455
|
+
# This endpoint also allows you to add a new email address to an existing Stytch User. Including a `user_id`, `session_token`, or `session_jwt` in your Send one-time passcode by email request will add the new, unverified email address to the existing Stytch User. If the user successfully authenticates within 5 minutes, the new email address will be marked as verified and remain permanently on the existing Stytch User. Otherwise, it will be removed from the User object, and any subsequent login requests using that email address will create a new User.
|
448
456
|
#
|
449
457
|
# ### Next steps
|
450
458
|
# Collect the OTP which was delivered to the user. Call [Authenticate OTP](https://stytch.com/docs/api/authenticate-otp) using the OTP `code` along with the `phone_id` found in the response as the `method_id`.
|
@@ -508,6 +516,7 @@ module Stytch
|
|
508
516
|
login_template_id: nil,
|
509
517
|
signup_template_id: nil
|
510
518
|
)
|
519
|
+
headers = {}
|
511
520
|
request = {
|
512
521
|
email: email
|
513
522
|
}
|
@@ -520,7 +529,7 @@ module Stytch
|
|
520
529
|
request[:login_template_id] = login_template_id unless login_template_id.nil?
|
521
530
|
request[:signup_template_id] = signup_template_id unless signup_template_id.nil?
|
522
531
|
|
523
|
-
post_request('/v1/otps/email/send', request)
|
532
|
+
post_request('/v1/otps/email/send', request, headers)
|
524
533
|
end
|
525
534
|
|
526
535
|
# Send a one-time passcode (OTP) to a User using their email. If the email is not associated with a User already, a User will be created.
|
@@ -587,6 +596,7 @@ module Stytch
|
|
587
596
|
login_template_id: nil,
|
588
597
|
signup_template_id: nil
|
589
598
|
)
|
599
|
+
headers = {}
|
590
600
|
request = {
|
591
601
|
email: email
|
592
602
|
}
|
@@ -597,7 +607,7 @@ module Stytch
|
|
597
607
|
request[:login_template_id] = login_template_id unless login_template_id.nil?
|
598
608
|
request[:signup_template_id] = signup_template_id unless signup_template_id.nil?
|
599
609
|
|
600
|
-
post_request('/v1/otps/email/login_or_create', request)
|
610
|
+
post_request('/v1/otps/email/login_or_create', request, headers)
|
601
611
|
end
|
602
612
|
end
|
603
613
|
end
|
data/lib/stytch/passwords.rb
CHANGED
@@ -100,6 +100,7 @@ module Stytch
|
|
100
100
|
untrusted_metadata: nil,
|
101
101
|
name: nil
|
102
102
|
)
|
103
|
+
headers = {}
|
103
104
|
request = {
|
104
105
|
email: email,
|
105
106
|
password: password
|
@@ -110,7 +111,7 @@ module Stytch
|
|
110
111
|
request[:untrusted_metadata] = untrusted_metadata unless untrusted_metadata.nil?
|
111
112
|
request[:name] = name unless name.nil?
|
112
113
|
|
113
|
-
post_request('/v1/passwords', request)
|
114
|
+
post_request('/v1/passwords', request, headers)
|
114
115
|
end
|
115
116
|
|
116
117
|
# Authenticate a user with their email address and password. This endpoint verifies that the user has a password currently set, and that the entered password is correct. There are two instances where the endpoint will return a `reset_password` error even if they enter their previous password:
|
@@ -185,6 +186,7 @@ module Stytch
|
|
185
186
|
session_jwt: nil,
|
186
187
|
session_custom_claims: nil
|
187
188
|
)
|
189
|
+
headers = {}
|
188
190
|
request = {
|
189
191
|
email: email,
|
190
192
|
password: password
|
@@ -194,7 +196,7 @@ module Stytch
|
|
194
196
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
195
197
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
196
198
|
|
197
|
-
post_request('/v1/passwords/authenticate', request)
|
199
|
+
post_request('/v1/passwords/authenticate', request, headers)
|
198
200
|
end
|
199
201
|
|
200
202
|
# This API allows you to check whether or not the user’s provided password is valid, and to provide feedback to the user on how to increase the strength of their password.
|
@@ -248,12 +250,13 @@ module Stytch
|
|
248
250
|
password:,
|
249
251
|
email: nil
|
250
252
|
)
|
253
|
+
headers = {}
|
251
254
|
request = {
|
252
255
|
password: password
|
253
256
|
}
|
254
257
|
request[:email] = email unless email.nil?
|
255
258
|
|
256
|
-
post_request('/v1/passwords/strength_check', request)
|
259
|
+
post_request('/v1/passwords/strength_check', request, headers)
|
257
260
|
end
|
258
261
|
|
259
262
|
# Adds an existing password to a User's email that doesn't have a password yet. We support migrating users from passwords stored with `bcrypt`, `scrypt`, `argon2`, `MD-5`, `SHA-1`, or `PBKDF2`. This endpoint has a rate limit of 100 requests per second.
|
@@ -289,6 +292,11 @@ module Stytch
|
|
289
292
|
# untrusted_metadata::
|
290
293
|
# The `untrusted_metadata` field contains an arbitrary JSON object of application-specific data. Untrusted metadata can be edited by end users directly via the SDK, and **cannot be used to store critical information.** See the [Metadata](https://stytch.com/docs/api/metadata) reference for complete field behavior details.
|
291
294
|
# The type of this field is nilable +object+.
|
295
|
+
# set_email_verified::
|
296
|
+
# Whether to set the user's email as verified. This is a dangerous field. Incorrect use may lead to users getting erroneously
|
297
|
+
# deduplicated into one user object. This flag should only be set if you can attest that the user owns the email address in question.
|
298
|
+
# Access to this field is restricted. To enable it, please send us a note at support@stytch.com.
|
299
|
+
# The type of this field is nilable +Boolean+.
|
292
300
|
# name::
|
293
301
|
# The name of the user. Each field in the name object is optional.
|
294
302
|
# The type of this field is nilable +Name+ (+object+).
|
@@ -324,8 +332,10 @@ module Stytch
|
|
324
332
|
pbkdf_2_config: nil,
|
325
333
|
trusted_metadata: nil,
|
326
334
|
untrusted_metadata: nil,
|
335
|
+
set_email_verified: nil,
|
327
336
|
name: nil
|
328
337
|
)
|
338
|
+
headers = {}
|
329
339
|
request = {
|
330
340
|
email: email,
|
331
341
|
hash: hash,
|
@@ -338,9 +348,10 @@ module Stytch
|
|
338
348
|
request[:pbkdf_2_config] = pbkdf_2_config unless pbkdf_2_config.nil?
|
339
349
|
request[:trusted_metadata] = trusted_metadata unless trusted_metadata.nil?
|
340
350
|
request[:untrusted_metadata] = untrusted_metadata unless untrusted_metadata.nil?
|
351
|
+
request[:set_email_verified] = set_email_verified unless set_email_verified.nil?
|
341
352
|
request[:name] = name unless name.nil?
|
342
353
|
|
343
|
-
post_request('/v1/passwords/migrate', request)
|
354
|
+
post_request('/v1/passwords/migrate', request, headers)
|
344
355
|
end
|
345
356
|
|
346
357
|
class Email
|
@@ -415,30 +426,34 @@ module Stytch
|
|
415
426
|
locale: nil,
|
416
427
|
reset_password_template_id: nil
|
417
428
|
)
|
429
|
+
headers = {}
|
418
430
|
request = {
|
419
431
|
email: email
|
420
432
|
}
|
421
433
|
request[:reset_password_redirect_url] = reset_password_redirect_url unless reset_password_redirect_url.nil?
|
422
|
-
unless reset_password_expiration_minutes.nil?
|
423
|
-
request[:reset_password_expiration_minutes] =
|
424
|
-
reset_password_expiration_minutes
|
425
|
-
end
|
434
|
+
request[:reset_password_expiration_minutes] = reset_password_expiration_minutes unless reset_password_expiration_minutes.nil?
|
426
435
|
request[:code_challenge] = code_challenge unless code_challenge.nil?
|
427
436
|
request[:attributes] = attributes unless attributes.nil?
|
428
437
|
request[:login_redirect_url] = login_redirect_url unless login_redirect_url.nil?
|
429
438
|
request[:locale] = locale unless locale.nil?
|
430
439
|
request[:reset_password_template_id] = reset_password_template_id unless reset_password_template_id.nil?
|
431
440
|
|
432
|
-
post_request('/v1/passwords/email/reset/start', request)
|
441
|
+
post_request('/v1/passwords/email/reset/start', request, headers)
|
433
442
|
end
|
434
443
|
|
435
444
|
# Reset the user’s password and authenticate them. This endpoint checks that the magic link `token` is valid, hasn’t expired, or already been used – and can optionally require additional security settings, such as the IP address and user agent matching the initial reset request.
|
436
445
|
#
|
437
446
|
# The provided password needs to meet our password strength requirements, which can be checked in advance with the password strength endpoint. If the token and password are accepted, the password is securely stored for future authentication and the user is authenticated.
|
438
447
|
#
|
448
|
+
# Note that a successful password reset by email will revoke all active sessions for the `user_id`.
|
449
|
+
#
|
439
450
|
# == Parameters:
|
440
451
|
# token::
|
441
|
-
# The token
|
452
|
+
# The Passwords `token` from the `?token=` query parameter in the URL.
|
453
|
+
#
|
454
|
+
# In the redirect URL, the `stytch_token_type` will be `login` or `reset_password`.
|
455
|
+
#
|
456
|
+
# See examples and read more about redirect URLs [here](https://stytch.com/docs/guides/dashboard/redirect-urls).
|
442
457
|
# The type of this field is +String+.
|
443
458
|
# password::
|
444
459
|
# The password of the user
|
@@ -512,6 +527,7 @@ module Stytch
|
|
512
527
|
attributes: nil,
|
513
528
|
options: nil
|
514
529
|
)
|
530
|
+
headers = {}
|
515
531
|
request = {
|
516
532
|
token: token,
|
517
533
|
password: password
|
@@ -524,7 +540,7 @@ module Stytch
|
|
524
540
|
request[:attributes] = attributes unless attributes.nil?
|
525
541
|
request[:options] = options unless options.nil?
|
526
542
|
|
527
|
-
post_request('/v1/passwords/email/reset', request)
|
543
|
+
post_request('/v1/passwords/email/reset', request, headers)
|
528
544
|
end
|
529
545
|
end
|
530
546
|
|
@@ -537,6 +553,8 @@ module Stytch
|
|
537
553
|
|
538
554
|
# Reset the User’s password using their existing password.
|
539
555
|
#
|
556
|
+
# Note that a successful password reset via an existing password will revoke all active sessions for the `user_id`.
|
557
|
+
#
|
540
558
|
# == Parameters:
|
541
559
|
# email::
|
542
560
|
# The email address of the end user.
|
@@ -605,6 +623,7 @@ module Stytch
|
|
605
623
|
session_jwt: nil,
|
606
624
|
session_custom_claims: nil
|
607
625
|
)
|
626
|
+
headers = {}
|
608
627
|
request = {
|
609
628
|
email: email,
|
610
629
|
existing_password: existing_password,
|
@@ -615,7 +634,7 @@ module Stytch
|
|
615
634
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
616
635
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
617
636
|
|
618
|
-
post_request('/v1/passwords/existing_password/reset', request)
|
637
|
+
post_request('/v1/passwords/existing_password/reset', request, headers)
|
619
638
|
end
|
620
639
|
end
|
621
640
|
|
@@ -628,6 +647,8 @@ module Stytch
|
|
628
647
|
|
629
648
|
# Reset the user’s password using their existing session. The endpoint will error if the session does not have a password, email magic link, or email OTP authentication factor that has been issued within the last 5 minutes. This endpoint requires either a `session_jwt` or `session_token` be included in the request.
|
630
649
|
#
|
650
|
+
# Note that a successful password reset via an existing session will revoke all active sessions for the `user_id`, except for the one used during the reset flow.
|
651
|
+
#
|
631
652
|
# == Parameters:
|
632
653
|
# password::
|
633
654
|
# The password of the user
|
@@ -638,6 +659,22 @@ module Stytch
|
|
638
659
|
# session_jwt::
|
639
660
|
# The `session_jwt` associated with a User's existing Session.
|
640
661
|
# The type of this field is nilable +String+.
|
662
|
+
# session_duration_minutes::
|
663
|
+
# Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist,
|
664
|
+
# returning both an opaque `session_token` and `session_jwt` for this session. Remember that the `session_jwt` will have a fixed lifetime of
|
665
|
+
# five minutes regardless of the underlying session duration, and will need to be refreshed over time.
|
666
|
+
#
|
667
|
+
# This value must be a minimum of 5 and a maximum of 527040 minutes (366 days).
|
668
|
+
#
|
669
|
+
# If a `session_token` or `session_jwt` is provided then a successful authentication will continue to extend the session this many minutes.
|
670
|
+
#
|
671
|
+
# If the `session_duration_minutes` parameter is not specified, a Stytch session will not be created.
|
672
|
+
# The type of this field is nilable +Integer+.
|
673
|
+
# session_custom_claims::
|
674
|
+
# Add a custom claims map to the Session being authenticated. Claims are only created if a Session is initialized by providing a value in `session_duration_minutes`. Claims will be included on the Session object and in the JWT. To update a key in an existing Session, supply a new value. To delete a key, supply a null value.
|
675
|
+
#
|
676
|
+
# Custom claims made with reserved claims ("iss", "sub", "aud", "exp", "nbf", "iat", "jti") will be ignored. Total custom claims size cannot exceed four kilobytes.
|
677
|
+
# The type of this field is nilable +object+.
|
641
678
|
#
|
642
679
|
# == Returns:
|
643
680
|
# An object with the following fields:
|
@@ -650,6 +687,12 @@ module Stytch
|
|
650
687
|
# user::
|
651
688
|
# 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.
|
652
689
|
# The type of this field is +User+ (+object+).
|
690
|
+
# session_token::
|
691
|
+
# A secret token for a given Stytch Session.
|
692
|
+
# The type of this field is +String+.
|
693
|
+
# session_jwt::
|
694
|
+
# The JSON Web Token (JWT) for a given Stytch Session.
|
695
|
+
# The type of this field is +String+.
|
653
696
|
# status_code::
|
654
697
|
# 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.
|
655
698
|
# The type of this field is +Integer+.
|
@@ -662,15 +705,20 @@ module Stytch
|
|
662
705
|
def reset(
|
663
706
|
password:,
|
664
707
|
session_token: nil,
|
665
|
-
session_jwt: nil
|
708
|
+
session_jwt: nil,
|
709
|
+
session_duration_minutes: nil,
|
710
|
+
session_custom_claims: nil
|
666
711
|
)
|
712
|
+
headers = {}
|
667
713
|
request = {
|
668
714
|
password: password
|
669
715
|
}
|
670
716
|
request[:session_token] = session_token unless session_token.nil?
|
671
717
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
718
|
+
request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
|
719
|
+
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
672
720
|
|
673
|
-
post_request('/v1/passwords/session/reset', request)
|
721
|
+
post_request('/v1/passwords/session/reset', request, headers)
|
674
722
|
end
|
675
723
|
end
|
676
724
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'errors'
|
4
|
+
require_relative 'request_helper'
|
5
|
+
|
6
|
+
module StytchB2B
|
7
|
+
class PolicyCache
|
8
|
+
def initialize(rbac_client:)
|
9
|
+
@rbac_client = rbac_client
|
10
|
+
@policy_last_update = 0
|
11
|
+
@cached_policy = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def reload_policy
|
15
|
+
@cached_policy = @rbac_client.policy['policy']
|
16
|
+
@policy_last_update = Time.now.to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_policy(invalidate: false)
|
20
|
+
reload_policy if invalidate || @cached_policy.nil? || @policy_last_update < Time.now.to_i - 300
|
21
|
+
@cached_policy
|
22
|
+
end
|
23
|
+
|
24
|
+
# Performs an authorization check against the project's policy and a set of roles. If the
|
25
|
+
# check succeeds, this method will return. If the check fails, a PermissionError
|
26
|
+
# will be raised. It's also possible for a TenancyError to be raised if the
|
27
|
+
# subject_org_id does not match the authZ request organization_id.
|
28
|
+
# authorization_check is an object with keys 'action', 'resource_id', and 'organization_id'
|
29
|
+
def perform_authorization_check(
|
30
|
+
subject_roles:,
|
31
|
+
subject_org_id:,
|
32
|
+
authorization_check:
|
33
|
+
)
|
34
|
+
request_org_id = authorization_check['organization_id']
|
35
|
+
raise Stytch::TenancyError.new(subject_org_id, request_org_id) if request_org_id != subject_org_id
|
36
|
+
|
37
|
+
policy = get_policy
|
38
|
+
|
39
|
+
for role in policy['roles']
|
40
|
+
next unless subject_roles.include?(role['role_id'])
|
41
|
+
|
42
|
+
for permission in role['permissions']
|
43
|
+
actions = permission['actions']
|
44
|
+
resource = permission['resource_id']
|
45
|
+
has_matching_action = actions.include?('*') || actions.include?(authorization_check['action'])
|
46
|
+
has_matching_resource = resource == authorization_check['resource_id']
|
47
|
+
if has_matching_action && has_matching_resource
|
48
|
+
# All good
|
49
|
+
return
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# If we get here, we didn't find a matching permission
|
55
|
+
raise Stytch::PermissionError, authorization_check
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -2,29 +2,33 @@
|
|
2
2
|
|
3
3
|
module Stytch
|
4
4
|
module RequestHelper
|
5
|
-
def get_request(path)
|
5
|
+
def get_request(path, headers)
|
6
6
|
@connection.get(
|
7
|
-
path
|
7
|
+
path,
|
8
|
+
headers
|
8
9
|
).body
|
9
10
|
end
|
10
11
|
|
11
|
-
def post_request(path, payload)
|
12
|
+
def post_request(path, payload, headers)
|
12
13
|
@connection.post(
|
13
14
|
path,
|
14
|
-
payload
|
15
|
+
payload,
|
16
|
+
headers
|
15
17
|
).body
|
16
18
|
end
|
17
19
|
|
18
|
-
def put_request(path, payload)
|
20
|
+
def put_request(path, payload, headers)
|
19
21
|
@connection.put(
|
20
22
|
path,
|
21
|
-
payload
|
23
|
+
payload,
|
24
|
+
headers
|
22
25
|
).body
|
23
26
|
end
|
24
27
|
|
25
|
-
def delete_request(path)
|
28
|
+
def delete_request(path, headers)
|
26
29
|
@connection.delete(
|
27
|
-
path
|
30
|
+
path,
|
31
|
+
headers
|
28
32
|
).body
|
29
33
|
end
|
30
34
|
|
data/lib/stytch/sessions.rb
CHANGED
@@ -54,11 +54,12 @@ module Stytch
|
|
54
54
|
def get(
|
55
55
|
user_id:
|
56
56
|
)
|
57
|
+
headers = {}
|
57
58
|
query_params = {
|
58
59
|
user_id: user_id
|
59
60
|
}
|
60
61
|
request = request_with_query_params('/v1/sessions', query_params)
|
61
|
-
get_request(request)
|
62
|
+
get_request(request, headers)
|
62
63
|
end
|
63
64
|
|
64
65
|
# Authenticate a session token and retrieve associated session data. If `session_duration_minutes` is included, update the lifetime of the session to be that many minutes from now. All timestamps are formatted according to the RFC 3339 standard and are expressed in UTC, e.g. `2021-12-29T12:33:09Z`. This endpoint requires exactly one `session_jwt` or `session_token` as part of the request. If both are included you will receive a `too_many_session_arguments` error.
|
@@ -108,13 +109,14 @@ module Stytch
|
|
108
109
|
session_jwt: nil,
|
109
110
|
session_custom_claims: nil
|
110
111
|
)
|
112
|
+
headers = {}
|
111
113
|
request = {}
|
112
114
|
request[:session_token] = session_token unless session_token.nil?
|
113
115
|
request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
|
114
116
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
115
117
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
116
118
|
|
117
|
-
post_request('/v1/sessions/authenticate', request)
|
119
|
+
post_request('/v1/sessions/authenticate', request, headers)
|
118
120
|
end
|
119
121
|
|
120
122
|
# Revoke a Session, immediately invalidating all of its session tokens. You can revoke a session in three ways: using its ID, or using one of its session tokens, or one of its JWTs. This endpoint requires exactly one of those to be included in the request. It will return an error if multiple are present.
|
@@ -143,15 +145,24 @@ module Stytch
|
|
143
145
|
session_token: nil,
|
144
146
|
session_jwt: nil
|
145
147
|
)
|
148
|
+
headers = {}
|
146
149
|
request = {}
|
147
150
|
request[:session_id] = session_id unless session_id.nil?
|
148
151
|
request[:session_token] = session_token unless session_token.nil?
|
149
152
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
150
153
|
|
151
|
-
post_request('/v1/sessions/revoke', request)
|
154
|
+
post_request('/v1/sessions/revoke', request, headers)
|
152
155
|
end
|
153
156
|
|
154
|
-
# Get the JSON Web Key Set (JWKS) for a
|
157
|
+
# Get the JSON Web Key Set (JWKS) for a project.
|
158
|
+
#
|
159
|
+
# JWKS are rotated every ~6 months. Upon rotation, new JWTs will be signed using the new key set, and both key sets will be returned by this endpoint for a period of 1 month.
|
160
|
+
#
|
161
|
+
# JWTs have a set lifetime of 5 minutes, so there will be a 5 minute period where some JWTs will be signed by the old JWKS, and some JWTs will be signed by the new JWKS. The correct JWKS to use for validation is determined by matching the `kid` value of the JWT and JWKS.
|
162
|
+
#
|
163
|
+
# If you're using one of our [backend SDKs](https://stytch.com/docs/sdks), the JWKS roll will be handled for you.
|
164
|
+
#
|
165
|
+
# 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.
|
155
166
|
#
|
156
167
|
# == Parameters:
|
157
168
|
# project_id::
|
@@ -172,9 +183,10 @@ module Stytch
|
|
172
183
|
def get_jwks(
|
173
184
|
project_id:
|
174
185
|
)
|
186
|
+
headers = {}
|
175
187
|
query_params = {}
|
176
188
|
request = request_with_query_params("/v1/sessions/jwks/#{project_id}", query_params)
|
177
|
-
get_request(request)
|
189
|
+
get_request(request, headers)
|
178
190
|
end
|
179
191
|
|
180
192
|
# MANUAL(Sessions::authenticate_jwt)(SERVICE_METHOD)
|
@@ -186,12 +198,15 @@ module Stytch
|
|
186
198
|
# If max_token_age_seconds is set and the JWT was issued (based on the "iat" claim) less than
|
187
199
|
# max_token_age_seconds seconds ago, then just verify locally and don't call the API
|
188
200
|
# To force remote validation for all tokens, set max_token_age_seconds to 0 or call authenticate()
|
201
|
+
# If max_token_age_seconds is not supplied 300 seconds will be used as the default.
|
189
202
|
def authenticate_jwt(
|
190
203
|
session_jwt,
|
191
204
|
max_token_age_seconds: nil,
|
192
205
|
session_duration_minutes: nil,
|
193
206
|
session_custom_claims: nil
|
194
207
|
)
|
208
|
+
max_token_age_seconds = 300 if max_token_age_seconds.nil?
|
209
|
+
|
195
210
|
if max_token_age_seconds == 0
|
196
211
|
return authenticate(
|
197
212
|
session_jwt: session_jwt,
|
@@ -200,18 +215,14 @@ module Stytch
|
|
200
215
|
)
|
201
216
|
end
|
202
217
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
session_duration_minutes: session_duration_minutes,
|
212
|
-
session_custom_claims: session_custom_claims
|
213
|
-
)
|
214
|
-
end
|
218
|
+
session = authenticate_jwt_local(session_jwt, max_token_age_seconds: max_token_age_seconds)
|
219
|
+
return session unless session.nil?
|
220
|
+
|
221
|
+
authenticate(
|
222
|
+
session_jwt: session_jwt,
|
223
|
+
session_duration_minutes: session_duration_minutes,
|
224
|
+
session_custom_claims: session_custom_claims
|
225
|
+
)
|
215
226
|
rescue StandardError
|
216
227
|
# JWT could not be verified locally. Check with the Stytch API.
|
217
228
|
authenticate(
|
@@ -225,12 +236,20 @@ module Stytch
|
|
225
236
|
# Uses the cached value to get the JWK but if it is unavailable, it calls the get_jwks()
|
226
237
|
# function to get the JWK
|
227
238
|
# This method never authenticates a JWT directly with the API
|
228
|
-
|
239
|
+
# If max_token_age_seconds is not supplied 300 seconds will be used as the default.
|
240
|
+
def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil)
|
241
|
+
max_token_age_seconds = 300 if max_token_age_seconds.nil?
|
242
|
+
|
229
243
|
issuer = 'stytch.com/' + @project_id
|
230
244
|
begin
|
231
245
|
decoded_token = JWT.decode session_jwt, nil, true,
|
232
246
|
{ jwks: @jwks_loader, iss: issuer, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'] }
|
233
|
-
|
247
|
+
|
248
|
+
session = decoded_token[0]
|
249
|
+
iat_time = Time.at(session['iat']).to_datetime
|
250
|
+
return nil unless iat_time + max_token_age_seconds >= Time.now
|
251
|
+
|
252
|
+
session = marshal_jwt_into_session(session)
|
234
253
|
rescue JWT::InvalidIssuerError
|
235
254
|
raise JWTInvalidIssuerError
|
236
255
|
rescue JWT::InvalidAudError
|
@@ -240,6 +259,8 @@ module Stytch
|
|
240
259
|
rescue JWT::IncorrectAlgorithm
|
241
260
|
raise JWTIncorrectAlgorithmError
|
242
261
|
end
|
262
|
+
|
263
|
+
session
|
243
264
|
end
|
244
265
|
|
245
266
|
def marshal_jwt_into_session(jwt)
|
@@ -251,15 +272,17 @@ module Stytch
|
|
251
272
|
reserved_claims = ['aud', 'exp', 'iat', 'iss', 'jti', 'nbf', 'sub', stytch_claim]
|
252
273
|
custom_claims = jwt.reject { |key, _| reserved_claims.include?(key) }
|
253
274
|
{
|
254
|
-
'
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
275
|
+
'session' => {
|
276
|
+
'session_id' => jwt[stytch_claim]['id'],
|
277
|
+
'user_id' => jwt['sub'],
|
278
|
+
'started_at' => jwt[stytch_claim]['started_at'],
|
279
|
+
'last_accessed_at' => jwt[stytch_claim]['last_accessed_at'],
|
280
|
+
# For JWTs that include it, prefer the inner expires_at claim.
|
281
|
+
'expires_at' => expires_at,
|
282
|
+
'attributes' => jwt[stytch_claim]['attributes'],
|
283
|
+
'authentication_factors' => jwt[stytch_claim]['authentication_factors'],
|
284
|
+
'custom_claims' => custom_claims
|
285
|
+
}
|
263
286
|
}
|
264
287
|
end
|
265
288
|
# ENDMANUAL(Sessions::authenticate_jwt)
|