Skip to content

Commit feb747f

Browse files
committed
Update
1 parent 91efaaf commit feb747f

File tree

4 files changed

+114
-13
lines changed

4 files changed

+114
-13
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import Optional
2+
3+
from stytch.consumer.api.rbac import RBAC
4+
from stytch.consumer.models.rbac import Policy
5+
from stytch.shared.lazy_cache import LazyCache
6+
7+
DEFAULT_REFRESH_INTERVAL = 10 * 60 # 10 minutes
8+
9+
10+
class PolicyCache(LazyCache[Policy]):
11+
def __init__(
12+
self,
13+
rbac: RBAC,
14+
refresh_interval_seconds: int = DEFAULT_REFRESH_INTERVAL,
15+
) -> None:
16+
self.rbac = rbac
17+
super().__init__(
18+
reload_func=self.reload_policy,
19+
async_reload_func=self.reload_policy_async,
20+
refresh_interval_seconds=refresh_interval_seconds,
21+
)
22+
23+
def reload_policy(self, _: Optional[Policy]) -> Policy:
24+
resp = self.rbac.policy()
25+
assert resp.policy is not None
26+
return resp.policy
27+
28+
async def reload_policy_async(self, _: Optional[Policy]) -> Policy:
29+
resp = await self.rbac.policy_async()
30+
assert resp.policy is not None
31+
return resp.policy

stytch/consumer/api/sessions.py

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,9 @@ def exchange_access_token(
298298
"""Use this endpoint to exchange a Connected Apps Access Token back into a Stytch Session for the underlying User.
299299
This session can be used with the Stytch SDKs and APIs.
300300
301-
The Access Token must contain the `full_access` scope and must not be more than 5 minutes old. Access Tokens may only be exchanged a single time.
301+
The Session returned will be the same Session that was active in your application (the authorizing party) during the initial authorization flow.
302+
303+
The Access Token must contain the `full_access` scope (only available to First Party clients) and must not be more than 5 minutes old. Access Tokens may only be exchanged a single time.
302304
303305
Fields:
304306
- access_token: The access token to exchange for a Stytch Session. Must be granted the `full_access` scope.
@@ -337,7 +339,9 @@ async def exchange_access_token_async(
337339
"""Use this endpoint to exchange a Connected Apps Access Token back into a Stytch Session for the underlying User.
338340
This session can be used with the Stytch SDKs and APIs.
339341
340-
The Access Token must contain the `full_access` scope and must not be more than 5 minutes old. Access Tokens may only be exchanged a single time.
342+
The Session returned will be the same Session that was active in your application (the authorizing party) during the initial authorization flow.
343+
344+
The Access Token must contain the `full_access` scope (only available to First Party clients) and must not be more than 5 minutes old. Access Tokens may only be exchanged a single time.
341345
342346
Fields:
343347
- access_token: The access token to exchange for a Stytch Session. Must be granted the `full_access` scope.
@@ -373,13 +377,13 @@ def get_jwks(
373377
) -> GetJWKSResponse:
374378
"""Get the JSON Web Key Set (JWKS) for a project.
375379
376-
JWKS are rotated every ~6 months. Upon rotation, new JWTs will be signed using the new key, and both keys will be returned by this endpoint for a period of 1 month.
380+
Within the JWKS, the JSON Web Keys are rotated every ~6 months. Upon rotation, new JWTs will be signed using the new key, and both keys will be returned by this endpoint for a period of 1 month.
377381
378-
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.
382+
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 keys, and some JWTs will be signed by the new keys. The correct key to use for validation is determined by matching the `kid` value of the JWT and key.
379383
380-
If you're using one of our [backend SDKs](https://stytch.com/docs/sdks), the JWKS rotation will be handled for you.
384+
If you're using one of our [backend SDKs](https://stytch.com/docs/b2b/sdks), the JSON Web Key (JWK) rotation will be handled for you.
381385
382-
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.
386+
If you're using your own JWT validation library, many have built-in support for JWK rotation, and you'll just need to supply this API endpoint. If not, your application should decide which JWK to use for validation by inspecting the `kid` value.
383387
384388
See our [How to use Stytch Session JWTs](https://stytch.com/docs/guides/sessions/using-jwts) guide for more information.
385389
@@ -401,13 +405,13 @@ async def get_jwks_async(
401405
) -> GetJWKSResponse:
402406
"""Get the JSON Web Key Set (JWKS) for a project.
403407
404-
JWKS are rotated every ~6 months. Upon rotation, new JWTs will be signed using the new key, and both keys will be returned by this endpoint for a period of 1 month.
408+
Within the JWKS, the JSON Web Keys are rotated every ~6 months. Upon rotation, new JWTs will be signed using the new key, and both keys will be returned by this endpoint for a period of 1 month.
405409
406-
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.
410+
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 keys, and some JWTs will be signed by the new keys. The correct key to use for validation is determined by matching the `kid` value of the JWT and key.
407411
408-
If you're using one of our [backend SDKs](https://stytch.com/docs/sdks), the JWKS rotation will be handled for you.
412+
If you're using one of our [backend SDKs](https://stytch.com/docs/b2b/sdks), the JSON Web Key (JWK) rotation will be handled for you.
409413
410-
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.
414+
If you're using your own JWT validation library, many have built-in support for JWK rotation, and you'll just need to supply this API endpoint. If not, your application should decide which JWK to use for validation by inspecting the `kid` value.
411415
412416
See our [How to use Stytch Session JWTs](https://stytch.com/docs/guides/sessions/using-jwts) guide for more information.
413417
@@ -432,6 +436,26 @@ def attest(
432436
session_token: Optional[str] = None,
433437
session_jwt: Optional[str] = None,
434438
) -> AttestResponse:
439+
"""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/docs/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.
440+
441+
Fields:
442+
- profile_id: The ID of the trusted auth token profile to use for attestation.
443+
- token: The trusted auth token to authenticate.
444+
- session_duration_minutes: Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist,
445+
returning both an opaque `session_token` and `session_jwt` for this session. Remember that the `session_jwt` will have a fixed lifetime of
446+
five minutes regardless of the underlying session duration, and will need to be refreshed over time.
447+
448+
This value must be a minimum of 5 and a maximum of 527040 minutes (366 days).
449+
450+
If a `session_token` or `session_jwt` is provided then a successful authentication will continue to extend the session this many minutes.
451+
452+
If the `session_duration_minutes` parameter is not specified, a Stytch session will not be created.
453+
- session_custom_claims: 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.
454+
455+
Custom claims made with reserved claims ("iss", "sub", "aud", "exp", "nbf", "iat", "jti") will be ignored. Total custom claims size cannot exceed four kilobytes.
456+
- session_token: The `session_token` for the session that you wish to add the trusted auth token authentication factor to.
457+
- session_jwt: The `session_jwt` for the session that you wish to add the trusted auth token authentication factor to.
458+
""" # noqa
435459
headers: Dict[str, str] = {}
436460
data: Dict[str, Any] = {
437461
"profile_id": profile_id,
@@ -459,6 +483,26 @@ async def attest_async(
459483
session_token: Optional[str] = None,
460484
session_jwt: Optional[str] = None,
461485
) -> AttestResponse:
486+
"""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/docs/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.
487+
488+
Fields:
489+
- profile_id: The ID of the trusted auth token profile to use for attestation.
490+
- token: The trusted auth token to authenticate.
491+
- session_duration_minutes: Set the session lifetime to be this many minutes from now. This will start a new session if one doesn't already exist,
492+
returning both an opaque `session_token` and `session_jwt` for this session. Remember that the `session_jwt` will have a fixed lifetime of
493+
five minutes regardless of the underlying session duration, and will need to be refreshed over time.
494+
495+
This value must be a minimum of 5 and a maximum of 527040 minutes (366 days).
496+
497+
If a `session_token` or `session_jwt` is provided then a successful authentication will continue to extend the session this many minutes.
498+
499+
If the `session_duration_minutes` parameter is not specified, a Stytch session will not be created.
500+
- session_custom_claims: 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.
501+
502+
Custom claims made with reserved claims ("iss", "sub", "aud", "exp", "nbf", "iat", "jti") will be ignored. Total custom claims size cannot exceed four kilobytes.
503+
- session_token: The `session_token` for the session that you wish to add the trusted auth token authentication factor to.
504+
- session_jwt: The `session_jwt` for the session that you wish to add the trusted auth token authentication factor to.
505+
""" # noqa
462506
headers: Dict[str, str] = {}
463507
data: Dict[str, Any] = {
464508
"profile_id": profile_id,
@@ -612,9 +656,9 @@ def authenticate_jwt_local(
612656
expires_at = claim.get("expires_at", generic_claims.reserved_claims["exp"])
613657

614658
if authorization_check is not None:
615-
rbac_local.perform_consumer_scope_authorization_check(
659+
rbac_local.perform_consumer_authorization_check(
616660
policy=self.policy_cache.get(),
617-
subject_roles=generic_claims.roles_claim,
661+
subject_roles=claim["roles"],
618662
authorization_check=authorization_check,
619663
)
620664

stytch/consumer/api/test/test_consumer_idp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from unittest.mock import Mock, patch, MagicMock, AsyncMock
55
from typing import Optional
66

7-
from stytch.b2b.models.rbac import (
7+
from stytch.consumer.models.rbac import (
88
Policy,
99
PolicyRole,
1010
PolicyRolePermission,

stytch/shared/rbac_local.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,32 @@ def perform_authorization_check(
6969
# If we made it here, we didn't find a matching permission
7070
raise RBACPermissionError(authorization_check)
7171

72+
def perform_consumer_authorization_check(
73+
policy: ConsumerPolicy,
74+
subject_roles: List[str],
75+
authorization_check: ConsumerAuthorizationCheck,
76+
) -> None:
77+
"""Performs an authorization check against a policy and a set of roles. If the check
78+
succeeds, this method will return. If the check fails, a PermissionError will be
79+
raised.
80+
"""
81+
for role in policy.roles:
82+
if role.role_id in subject_roles:
83+
for permission in role.permissions:
84+
has_matching_action = (
85+
"*" in permission.actions
86+
or authorization_check.action in permission.actions
87+
)
88+
has_matching_resource = (
89+
authorization_check.resource_id == permission.resource_id
90+
)
91+
if has_matching_action and has_matching_resource:
92+
# All good, we found a matching permission
93+
return
94+
95+
# If we made it here, we didn't find a matching permission
96+
raise RBACPermissionError(authorization_check)
97+
7298

7399
def perform_scope_authorization_check(
74100
policy: Policy,

0 commit comments

Comments
 (0)