stytch 10.25.0 → 10.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/stytch/b2b_client.rb +10 -2
- data/lib/stytch/b2b_discovery.rb +6 -6
- data/lib/stytch/b2b_idp.rb +266 -0
- data/lib/stytch/b2b_magic_links.rb +3 -3
- data/lib/stytch/b2b_oauth.rb +1 -1
- data/lib/stytch/b2b_organizations.rb +47 -35
- data/lib/stytch/b2b_otp.rb +9 -7
- data/lib/stytch/b2b_passwords.rb +10 -7
- data/lib/stytch/b2b_rbac.rb +2 -2
- data/lib/stytch/b2b_recovery_codes.rb +3 -3
- data/lib/stytch/b2b_scim.rb +8 -8
- data/lib/stytch/b2b_sessions.rb +6 -6
- data/lib/stytch/b2b_sso.rb +10 -10
- data/lib/stytch/b2b_totps.rb +6 -4
- data/lib/stytch/client.rb +16 -2
- data/lib/stytch/fraud.rb +6 -3
- data/lib/stytch/idp.rb +251 -0
- data/lib/stytch/otps.rb +2 -2
- data/lib/stytch/passwords.rb +4 -4
- data/lib/stytch/rbac.rb +49 -0
- data/lib/stytch/rbac_local.rb +66 -3
- data/lib/stytch/sessions.rb +37 -10
- data/lib/stytch/version.rb +1 -1
- data/lib/stytch.rb +1 -0
- metadata +5 -2
data/lib/stytch/rbac_local.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require_relative 'errors'
|
4
4
|
require_relative 'request_helper'
|
5
5
|
|
6
|
-
module
|
6
|
+
module Stytch
|
7
7
|
class PolicyCache
|
8
8
|
def initialize(rbac_client:)
|
9
9
|
@rbac_client = rbac_client
|
@@ -31,8 +31,7 @@ module StytchB2B
|
|
31
31
|
subject_org_id:,
|
32
32
|
authorization_check:
|
33
33
|
)
|
34
|
-
|
35
|
-
raise Stytch::TenancyError.new(subject_org_id, request_org_id) if request_org_id != subject_org_id
|
34
|
+
raise Stytch::TenancyError.new(subject_org_id, authorization_check['organization_id']) if subject_org_id != authorization_check['organization_id']
|
36
35
|
|
37
36
|
policy = get_policy
|
38
37
|
|
@@ -54,5 +53,69 @@ module StytchB2B
|
|
54
53
|
# If we get here, we didn't find a matching permission
|
55
54
|
raise Stytch::PermissionError, authorization_check
|
56
55
|
end
|
56
|
+
|
57
|
+
# Performs an authorization check against the project's policy and a set of roles. If the
|
58
|
+
# check succeeds, this method will return. If the check fails, a PermissionError
|
59
|
+
# will be raised. This is used for role based authorization.
|
60
|
+
def perform_consumer_authorization_check(
|
61
|
+
subject_roles:,
|
62
|
+
authorization_check:
|
63
|
+
)
|
64
|
+
policy = get_policy
|
65
|
+
|
66
|
+
# For consumer authorization, we check roles without tenancy validation
|
67
|
+
for role in policy['roles']
|
68
|
+
next unless subject_roles.include?(role['role_id'])
|
69
|
+
|
70
|
+
for permission in role['permissions']
|
71
|
+
actions = permission['actions']
|
72
|
+
resource = permission['resource_id']
|
73
|
+
has_matching_action = actions.include?('*') || actions.include?(authorization_check['action'])
|
74
|
+
has_matching_resource = resource == authorization_check['resource_id']
|
75
|
+
if has_matching_action && has_matching_resource
|
76
|
+
return # Permission granted
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# If we get here, we didn't find a matching permission
|
82
|
+
raise Stytch::PermissionError, authorization_check
|
83
|
+
end
|
84
|
+
|
85
|
+
# Performs an authorization check against the project's policy and a set of scopes. If the
|
86
|
+
# check succeeds, this method will return. If the check fails, a PermissionError
|
87
|
+
# will be raised. This is used for OAuth-style scope-based authorization.
|
88
|
+
# authorization_check is an object with keys 'action' and 'resource_id'
|
89
|
+
def perform_scope_authorization_check(
|
90
|
+
token_scopes:,
|
91
|
+
authorization_check:
|
92
|
+
)
|
93
|
+
policy = get_policy
|
94
|
+
|
95
|
+
# For scope-based authorization, we check if any of the token scopes match policy scopes
|
96
|
+
# and if those scopes grant permission for the requested action/resource
|
97
|
+
action = authorization_check['action']
|
98
|
+
resource_id = authorization_check['resource_id']
|
99
|
+
|
100
|
+
# Check if any of the token scopes grant permission for this action/resource
|
101
|
+
for scope_obj in policy['scopes']
|
102
|
+
scope_name = scope_obj['scope']
|
103
|
+
next unless token_scopes.include?(scope_name)
|
104
|
+
|
105
|
+
# Check if this scope grants permission for the requested action/resource
|
106
|
+
for permission in scope_obj['permissions']
|
107
|
+
actions = permission['actions']
|
108
|
+
resource = permission['resource_id']
|
109
|
+
has_matching_action = actions.include?('*') || actions.include?(action)
|
110
|
+
has_matching_resource = resource == resource_id
|
111
|
+
if has_matching_action && has_matching_resource
|
112
|
+
return # Permission granted
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# If we get here, we didn't find a matching permission
|
118
|
+
raise Stytch::PermissionError, authorization_check
|
119
|
+
end
|
57
120
|
end
|
58
121
|
end
|
data/lib/stytch/sessions.rb
CHANGED
@@ -15,9 +15,10 @@ module Stytch
|
|
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|
|
@@ -81,6 +82,13 @@ module Stytch
|
|
81
82
|
#
|
82
83
|
# Custom claims made with reserved claims ("iss", "sub", "aud", "exp", "nbf", "iat", "jti") will be ignored. Total custom claims size cannot exceed four kilobytes.
|
83
84
|
# The type of this field is nilable +object+.
|
85
|
+
# authorization_check::
|
86
|
+
# If an `authorization_check` object is passed in, this endpoint will also check if the User is
|
87
|
+
# authorized to perform the given action on the given Resource. A User is authorized if they are assigned a Role with adequate permissions.
|
88
|
+
#
|
89
|
+
# If the User is not authorized to perform the specified action on the specified Resource, a 403 error will be thrown.
|
90
|
+
# Otherwise, the response will contain a list of Roles that satisfied the authorization check.
|
91
|
+
# The type of this field is nilable +AuthorizationCheck+ (+object+).
|
84
92
|
#
|
85
93
|
# == Returns:
|
86
94
|
# An object with the following fields:
|
@@ -105,11 +113,16 @@ module Stytch
|
|
105
113
|
# status_code::
|
106
114
|
# 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.
|
107
115
|
# The type of this field is +Integer+.
|
116
|
+
# verdict::
|
117
|
+
# If an `authorization_check` is provided in the request and the check succeeds, this field will return
|
118
|
+
# information about why the User was granted permission.
|
119
|
+
# The type of this field is nilable +AuthorizationVerdict+ (+object+).
|
108
120
|
def authenticate(
|
109
121
|
session_token: nil,
|
110
122
|
session_duration_minutes: nil,
|
111
123
|
session_jwt: nil,
|
112
|
-
session_custom_claims: nil
|
124
|
+
session_custom_claims: nil,
|
125
|
+
authorization_check: nil
|
113
126
|
)
|
114
127
|
headers = {}
|
115
128
|
request = {}
|
@@ -117,6 +130,7 @@ module Stytch
|
|
117
130
|
request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
|
118
131
|
request[:session_jwt] = session_jwt unless session_jwt.nil?
|
119
132
|
request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
|
133
|
+
request[:authorization_check] = authorization_check unless authorization_check.nil?
|
120
134
|
|
121
135
|
post_request('/v1/sessions/authenticate', request, headers)
|
122
136
|
end
|
@@ -156,7 +170,7 @@ module Stytch
|
|
156
170
|
post_request('/v1/sessions/revoke', request, headers)
|
157
171
|
end
|
158
172
|
|
159
|
-
# Migrate a session from an external OIDC compliant endpoint. Stytch will call the external UserInfo endpoint defined in your Stytch Project settings in the [Dashboard](https://stytch.com/
|
173
|
+
# Migrate a session from an external OIDC compliant endpoint. Stytch will call the external UserInfo endpoint defined in your Stytch Project settings in the [Dashboard](https://stytch.com/dashboard), and then perform a lookup using the `session_token`. If the response contains a valid email address, Stytch will attempt to match that email address with an existing User and create a Stytch Session. You will need to create the user before using this endpoint.
|
160
174
|
#
|
161
175
|
# == Parameters:
|
162
176
|
# session_token::
|
@@ -326,7 +340,7 @@ module Stytch
|
|
326
340
|
get_request(request, headers)
|
327
341
|
end
|
328
342
|
|
329
|
-
# Exchange an auth token issued by a trusted identity provider for a Stytch session. You must first register a Trusted Auth Token profile in the Stytch dashboard [here](https://stytch.com/
|
343
|
+
# Exchange an auth token issued by a trusted identity provider for a Stytch session. You must first register a Trusted Auth Token profile in the Stytch dashboard [here](https://stytch.com/dashboard/trusted-auth-tokens). If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.
|
330
344
|
#
|
331
345
|
# == Parameters:
|
332
346
|
# profile_id::
|
@@ -421,7 +435,8 @@ module Stytch
|
|
421
435
|
max_token_age_seconds: nil,
|
422
436
|
session_duration_minutes: nil,
|
423
437
|
session_custom_claims: nil,
|
424
|
-
clock_tolerance_seconds: nil
|
438
|
+
clock_tolerance_seconds: nil,
|
439
|
+
authorization_check: nil
|
425
440
|
)
|
426
441
|
max_token_age_seconds = 300 if max_token_age_seconds.nil?
|
427
442
|
clock_tolerance_seconds = 0 if clock_tolerance_seconds.nil?
|
@@ -430,28 +445,32 @@ module Stytch
|
|
430
445
|
return authenticate(
|
431
446
|
session_jwt: session_jwt,
|
432
447
|
session_duration_minutes: session_duration_minutes,
|
433
|
-
session_custom_claims: session_custom_claims
|
448
|
+
session_custom_claims: session_custom_claims,
|
449
|
+
authorization_check: authorization_check
|
434
450
|
)
|
435
451
|
end
|
436
452
|
|
437
453
|
session = authenticate_jwt_local(
|
438
454
|
session_jwt,
|
439
455
|
max_token_age_seconds: max_token_age_seconds,
|
440
|
-
clock_tolerance_seconds: clock_tolerance_seconds
|
456
|
+
clock_tolerance_seconds: clock_tolerance_seconds,
|
457
|
+
authorization_check: authorization_check
|
441
458
|
)
|
442
459
|
return session unless session.nil?
|
443
460
|
|
444
461
|
authenticate(
|
445
462
|
session_jwt: session_jwt,
|
446
463
|
session_duration_minutes: session_duration_minutes,
|
447
|
-
session_custom_claims: session_custom_claims
|
464
|
+
session_custom_claims: session_custom_claims,
|
465
|
+
authorization_check: authorization_check
|
448
466
|
)
|
449
467
|
rescue StandardError
|
450
468
|
# JWT could not be verified locally. Check with the Stytch API.
|
451
469
|
authenticate(
|
452
470
|
session_jwt: session_jwt,
|
453
471
|
session_duration_minutes: session_duration_minutes,
|
454
|
-
session_custom_claims: session_custom_claims
|
472
|
+
session_custom_claims: session_custom_claims,
|
473
|
+
authorization_check: authorization_check
|
455
474
|
)
|
456
475
|
end
|
457
476
|
|
@@ -461,7 +480,7 @@ module Stytch
|
|
461
480
|
# This method never authenticates a JWT directly with the API
|
462
481
|
# If max_token_age_seconds is not supplied 300 seconds will be used as the default.
|
463
482
|
# If clock_tolerance_seconds is not supplied 0 seconds will be used as the default.
|
464
|
-
def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil, clock_tolerance_seconds: nil)
|
483
|
+
def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil, clock_tolerance_seconds: nil, authorization_check: nil)
|
465
484
|
max_token_age_seconds = 300 if max_token_age_seconds.nil?
|
466
485
|
clock_tolerance_seconds = 0 if clock_tolerance_seconds.nil?
|
467
486
|
|
@@ -488,6 +507,14 @@ module Stytch
|
|
488
507
|
raise JWTIncorrectAlgorithmError
|
489
508
|
end
|
490
509
|
|
510
|
+
# Do the auth check - intentionally don't rescue errors from here
|
511
|
+
if authorization_check
|
512
|
+
@policy_cache.perform_consumer_authorization_check(
|
513
|
+
subject_roles: session['roles'],
|
514
|
+
authorization_check: authorization_check
|
515
|
+
)
|
516
|
+
end
|
517
|
+
|
491
518
|
session
|
492
519
|
end
|
493
520
|
|
data/lib/stytch/version.rb
CHANGED
data/lib/stytch.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stytch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 10.
|
4
|
+
version: 10.27.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- stytch
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -126,6 +126,7 @@ files:
|
|
126
126
|
- lib/stytch.rb
|
127
127
|
- lib/stytch/b2b_client.rb
|
128
128
|
- lib/stytch/b2b_discovery.rb
|
129
|
+
- lib/stytch/b2b_idp.rb
|
129
130
|
- lib/stytch/b2b_impersonation.rb
|
130
131
|
- lib/stytch/b2b_magic_links.rb
|
131
132
|
- lib/stytch/b2b_oauth.rb
|
@@ -143,6 +144,7 @@ files:
|
|
143
144
|
- lib/stytch/crypto_wallets.rb
|
144
145
|
- lib/stytch/errors.rb
|
145
146
|
- lib/stytch/fraud.rb
|
147
|
+
- lib/stytch/idp.rb
|
146
148
|
- lib/stytch/impersonation.rb
|
147
149
|
- lib/stytch/m2m.rb
|
148
150
|
- lib/stytch/magic_links.rb
|
@@ -152,6 +154,7 @@ files:
|
|
152
154
|
- lib/stytch/otps.rb
|
153
155
|
- lib/stytch/passwords.rb
|
154
156
|
- lib/stytch/project.rb
|
157
|
+
- lib/stytch/rbac.rb
|
155
158
|
- lib/stytch/rbac_local.rb
|
156
159
|
- lib/stytch/request_helper.rb
|
157
160
|
- lib/stytch/sessions.rb
|