stytch 6.4.0 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
- # ### Add a phone number to an existing user
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
- # 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 the request will add the phone number to the pre-existing Stytch User upon successful authentication.
137
+ # ### Add a phone number to an existing user
135
138
  #
136
- # 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.
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 the request will add the phone number to the pre-existing Stytch User upon successful authentication.
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 the request will add the email to the pre-existing Stytch User upon successful authentication.
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
@@ -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 to authenticate.
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
 
@@ -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 Stytch Project.
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
- decoded_jwt = authenticate_jwt_local(session_jwt)
204
- iat_time = Time.at(decoded_jwt['iat']).to_datetime
205
- if iat_time + max_token_age_seconds >= Time.now
206
- session = marshal_jwt_into_session(decoded_jwt)
207
- { 'session' => session }
208
- else
209
- authenticate(
210
- session_jwt: session_jwt,
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
- def authenticate_jwt_local(session_jwt)
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
- decoded_token[0]
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
- 'session_id' => jwt[stytch_claim]['id'],
255
- 'user_id' => jwt['sub'],
256
- 'started_at' => jwt[stytch_claim]['started_at'],
257
- 'last_accessed_at' => jwt[stytch_claim]['last_accessed_at'],
258
- # For JWTs that include it, prefer the inner expires_at claim.
259
- 'expires_at' => expires_at,
260
- 'attributes' => jwt[stytch_claim]['attributes'],
261
- 'authentication_factors' => jwt[stytch_claim]['authentication_factors'],
262
- 'custom_claims' => custom_claims
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)