Encryptors 2.70__tar.gz → 2.72__tar.gz

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.
Files changed (90) hide show
  1. {encryptors-2.70 → encryptors-2.72}/PKG-INFO +1 -1
  2. {encryptors-2.70 → encryptors-2.72}/setup.py +1 -1
  3. {encryptors-2.70 → encryptors-2.72}/src/Encryptors.egg-info/PKG-INFO +1 -1
  4. {encryptors-2.70 → encryptors-2.72}/src/Encryptors.egg-info/SOURCES.txt +1 -0
  5. encryptors-2.72/src/Osdental/Enums/Secret.py +8 -0
  6. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Helpers/AuthTokenProcessor.py +6 -1
  7. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Helpers/JwtTokenHelper.py +2 -9
  8. encryptors-2.72/src/Osdental/Models/TokenClaims.py +80 -0
  9. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Services/JwtAuthTokenService.py +39 -62
  10. encryptors-2.70/src/Osdental/Models/TokenClaims.py +0 -47
  11. {encryptors-2.70 → encryptors-2.72}/README.md +0 -0
  12. {encryptors-2.70 → encryptors-2.72}/setup.cfg +0 -0
  13. {encryptors-2.70 → encryptors-2.72}/src/Encryptors.egg-info/dependency_links.txt +0 -0
  14. {encryptors-2.70 → encryptors-2.72}/src/Encryptors.egg-info/entry_points.txt +0 -0
  15. {encryptors-2.70 → encryptors-2.72}/src/Encryptors.egg-info/requires.txt +0 -0
  16. {encryptors-2.70 → encryptors-2.72}/src/Encryptors.egg-info/top_level.txt +0 -0
  17. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Cache/Redis.py +0 -0
  18. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Cache/__init__.py +0 -0
  19. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Cli/__init__.py +0 -0
  20. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Constants/Constant.py +0 -0
  21. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Constants/Message.py +0 -0
  22. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Constants/__init__.py +0 -0
  23. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Context/__init__.py +0 -0
  24. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Database/BaseRepository.py +0 -0
  25. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Database/Connection.py +0 -0
  26. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Database/__init__.py +0 -0
  27. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Decorators/Grpc.py +0 -0
  28. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Decorators/Retry.py +0 -0
  29. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Decorators/SecureResolver.py +0 -0
  30. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Decorators/__init__.py +0 -0
  31. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Encryptor/Aes.py +0 -0
  32. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Encryptor/Argon2.py +0 -0
  33. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Encryptor/Bcrypt.py +0 -0
  34. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Encryptor/Jwt.py +0 -0
  35. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Encryptor/Rsa.py +0 -0
  36. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Encryptor/Sha512.py +0 -0
  37. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Encryptor/__init__.py +0 -0
  38. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Enums/AuditType.py +0 -0
  39. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Enums/CipherType.py +0 -0
  40. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Enums/FileType.py +0 -0
  41. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Enums/GrahpqlOperation.py +0 -0
  42. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Enums/GrpcStatusCode.py +0 -0
  43. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Enums/Profile.py +0 -0
  44. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Enums/ResultType.py +0 -0
  45. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Enums/StatusCode.py +0 -0
  46. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Enums/__init__.py +0 -0
  47. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Exception/ControlledException.py +0 -0
  48. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Exception/__init__.py +0 -0
  49. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Helpers/AuditHelper.py +0 -0
  50. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Helpers/AuditQueue.py +0 -0
  51. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Helpers/AzureClassifier.py +0 -0
  52. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Helpers/CipherHelper.py +0 -0
  53. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Helpers/GrpcConnection.py +0 -0
  54. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Helpers/Resilience.py +0 -0
  55. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Helpers/__init__.py +0 -0
  56. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Http/APIClient.py +0 -0
  57. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Http/__init__.py +0 -0
  58. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
  59. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Messaging/Kafka.py +0 -0
  60. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Messaging/RabbitMQ.py +0 -0
  61. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Messaging/__init__.py +0 -0
  62. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Models/ApiResponse.py +0 -0
  63. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Models/AuditContext.py +0 -0
  64. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Models/Graphql.py +0 -0
  65. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Models/Notification.py +0 -0
  66. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Models/Response.py +0 -0
  67. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Models/Token.py +0 -0
  68. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Models/__init__.py +0 -0
  69. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Secrets/AzureKeyVaultProvider.py +0 -0
  70. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Secrets/__init__.py +0 -0
  71. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Services/ServiceBusAuditEmitter.py +0 -0
  72. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Services/WebsocketClient.py +0 -0
  73. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Services/__init__.py +0 -0
  74. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
  75. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Storage/S3Storage.py +0 -0
  76. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Storage/__init__.py +0 -0
  77. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/CaseConverter.py +0 -0
  78. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/CodeGenerator.py +0 -0
  79. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/DataNormalizer.py +0 -0
  80. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/DataUtils.py +0 -0
  81. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/DateUtils.py +0 -0
  82. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/FileMetaData.py +0 -0
  83. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/HashValidator.py +0 -0
  84. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/Mapper.py +0 -0
  85. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/PasswordGenerator.py +0 -0
  86. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/QueryGenerator.py +0 -0
  87. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/RsaUtils.py +0 -0
  88. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/TextProcessor.py +0 -0
  89. {encryptors-2.70 → encryptors-2.72}/src/Osdental/Utils/__init__.py +0 -0
  90. {encryptors-2.70 → encryptors-2.72}/src/Osdental/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Encryptors
3
- Version: 2.70
3
+ Version: 2.72
4
4
  Summary: End-to-end algorithm library
5
5
  Author: OSDental LLC
6
6
  Author-email: support@osdental.ai
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
  # ANDERSON REVISAR EL CACHE LOCAL DEL KEYVAULT PARA VALIDAR SI FUNCIONA
3
3
  setup(
4
4
  name="Encryptors",
5
- version="2.70",
5
+ version="2.72",
6
6
  author="OSDental LLC",
7
7
  author_email="support@osdental.ai",
8
8
  description="End-to-end algorithm library",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Encryptors
3
- Version: 2.70
3
+ Version: 2.72
4
4
  Summary: End-to-end algorithm library
5
5
  Author: OSDental LLC
6
6
  Author-email: support@osdental.ai
@@ -35,6 +35,7 @@ src/Osdental/Enums/GrahpqlOperation.py
35
35
  src/Osdental/Enums/GrpcStatusCode.py
36
36
  src/Osdental/Enums/Profile.py
37
37
  src/Osdental/Enums/ResultType.py
38
+ src/Osdental/Enums/Secret.py
38
39
  src/Osdental/Enums/StatusCode.py
39
40
  src/Osdental/Enums/__init__.py
40
41
  src/Osdental/Exception/ControlledException.py
@@ -0,0 +1,8 @@
1
+ from enum import StrEnum
2
+
3
+ class Secret(StrEnum):
4
+
5
+ ISSUER = "LEGACY-NAME"
6
+ AUDIENCE = "LEGACY-URL"
7
+ SECRET_KEY_1 = "SECRET-KEY-1"
8
+ PUBLIC_KEY_1 = "PUBLIC-KEY-1"
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  from typing import Dict, Any
3
3
  from uuid import UUID
4
+ from datetime import datetime, timezone, timedelta
4
5
  from graphql import OperationType
5
6
  from starlette.datastructures import Headers
6
7
  from Osdental.Encryptor.Aes import AES
@@ -111,4 +112,8 @@ def get_current_aes_key(context: BaseGraphQLContext):
111
112
  if context.access_token_claims:
112
113
  return context.access_token_claims.aes_key
113
114
 
114
- return None
115
+ return None
116
+
117
+ def get_token_exp(exp_minutes: int) -> datetime:
118
+ now = datetime.now(timezone.utc)
119
+ return now + timedelta(minutes=exp_minutes)
@@ -25,7 +25,7 @@ class JwtTokenHelper:
25
25
  def generate_token(
26
26
  self,
27
27
  claims: dict,
28
- expiration_minutes: int,
28
+ expires_at: datetime,
29
29
  ) -> str:
30
30
 
31
31
  now = datetime.now(timezone.utc)
@@ -34,14 +34,7 @@ class JwtTokenHelper:
34
34
  **claims,
35
35
  "iat": int(now.timestamp()),
36
36
  "nbf": int(now.timestamp()),
37
- "exp": int(
38
- (
39
- now
40
- + timedelta(
41
- minutes=expiration_minutes
42
- )
43
- ).timestamp()
44
- ),
37
+ "exp": int(expires_at.timestamp()),
45
38
  }
46
39
 
47
40
  # 1. Firmar → JWS (inner JWT)
@@ -0,0 +1,80 @@
1
+ from typing import Dict, Any
2
+ from dataclasses import dataclass, asdict
3
+ from typing import Optional
4
+
5
+ @dataclass(frozen=True, slots=True)
6
+ class TokenValidationOptions:
7
+ issuer: Optional[str] = None
8
+ audience: Optional[str] = None
9
+
10
+ validate_issuer: bool = True
11
+ validate_audience: bool = True
12
+
13
+ @dataclass(frozen=True, slots=True)
14
+ class BaseClaims:
15
+
16
+ def to_dict(self) -> Dict[str, Any]:
17
+ return asdict(self)
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class AccessTokenClaims(BaseClaims):
21
+
22
+ id_legacy: int
23
+ id_enterprise: int
24
+ aes_key: str
25
+ exp: Optional[int] = 0
26
+
27
+ @classmethod
28
+ def from_dict(cls, claims: dict):
29
+
30
+ return cls(
31
+ id_legacy=claims["id_legacy"],
32
+ id_enterprise=claims["id_enterprise"],
33
+ aes_key=claims["aes_key"],
34
+ exp=claims["exp"],
35
+ )
36
+
37
+ @dataclass(frozen=True, slots=True)
38
+ class UserTokenClaims(BaseClaims):
39
+
40
+ id_legacy: str
41
+ id_enterprise: str
42
+ id_token: str
43
+ id_user: str
44
+ id_external_enterprise: str
45
+ id_profile: str
46
+ user_full_name: str
47
+ abbreviation: str
48
+ aes_key_auth: str
49
+ exp: Optional[int] = 0
50
+
51
+ @classmethod
52
+ def from_dict(cls, claims: dict):
53
+
54
+ return cls(
55
+ id_legacy=claims["id_legacy"],
56
+ id_enterprise=claims["id_enterprise"],
57
+ id_token=claims["id_token"],
58
+ id_user=claims["id_user"],
59
+ id_external_enterprise=claims["id_external_enterprise"],
60
+ id_profile=claims["id_profile"],
61
+ user_full_name=claims["user_full_name"],
62
+ abbreviation=claims["abbreviation"],
63
+ aes_key_auth=claims["aes_key_auth"],
64
+ exp=claims["exp"],
65
+ )
66
+
67
+
68
+ @dataclass(frozen=True, slots=True)
69
+ class ActionTokenClaims(BaseClaims):
70
+
71
+ sub: str
72
+ action: Optional[str] = None
73
+
74
+ @classmethod
75
+ def from_dict(cls, claims: dict):
76
+
77
+ return cls(
78
+ sub=claims["sub"],
79
+ action=claims["action"],
80
+ )
@@ -1,17 +1,14 @@
1
1
  import asyncio
2
-
2
+ from datetime import datetime
3
3
  from Osdental.Models.TokenClaims import (
4
4
  BaseClaims, AccessTokenClaims, UserTokenClaims,
5
5
  ActionTokenClaims, TokenValidationOptions
6
6
  )
7
-
8
7
  from Osdental.Services import IAuthTokenService
9
-
10
8
  from Osdental.Helpers.JwtTokenHelper import JwtTokenHelper
11
-
12
9
  from Osdental.Utils.RsaUtils import normalize_rsa_keys
13
-
14
10
  from Osdental.Secrets import ISecretProvider
11
+ from Osdental.Enums.Secret import Secret
15
12
 
16
13
  class JwtAuthTokenService(IAuthTokenService):
17
14
 
@@ -27,36 +24,46 @@ class JwtAuthTokenService(IAuthTokenService):
27
24
 
28
25
  self._jwt = None
29
26
 
27
+ self._init_lock = asyncio.Lock()
28
+
30
29
 
31
- async def __get_token_signatures(self):
30
+ async def _ensure_initialized(self):
32
31
 
33
- issuer, audience, private_key, public_key = await asyncio.gather(
34
- self.secrets.get("LEGACY-NAME"),
35
- self.secrets.get("LEGACY-URL"),
36
- self.secrets.get("SECRET-KEY-1"),
37
- self.secrets.get("PUBLIC-KEY-1"),
38
- )
32
+ if self._jwt is not None:
33
+ return
34
+
35
+ async with self._init_lock:
36
+
37
+ if self._jwt is not None:
38
+ return
39
+
40
+ issuer, audience, private_key, public_key = await asyncio.gather(
41
+ self.secrets.get(Secret.ISSUER),
42
+ self.secrets.get(Secret.AUDIENCE),
43
+ self.secrets.get(Secret.SECRET_KEY_1),
44
+ self.secrets.get(Secret.PUBLIC_KEY_1),
45
+ )
39
46
 
40
- self._issuer = issuer
41
- self._audience = audience
47
+ self._issuer = issuer
48
+ self._audience = audience
42
49
 
43
- self._jwt = JwtTokenHelper(
44
- private_key_pem=normalize_rsa_keys(
45
- private_key
46
- ),
47
- public_key_pem=normalize_rsa_keys(
48
- public_key
50
+ self._jwt = JwtTokenHelper(
51
+ private_key_pem=normalize_rsa_keys(
52
+ private_key
53
+ ),
54
+ public_key_pem=normalize_rsa_keys(
55
+ public_key
56
+ )
49
57
  )
50
- )
51
58
 
52
59
 
53
60
  async def generate_token(
54
61
  self,
55
62
  claims: BaseClaims,
56
- expiration_minutes: int,
63
+ expires_at: datetime,
57
64
  ) -> str:
58
65
 
59
- await self.__get_token_signatures()
66
+ await self._ensure_initialized()
60
67
 
61
68
  claims = {
62
69
  **claims.to_dict(),
@@ -65,7 +72,7 @@ class JwtAuthTokenService(IAuthTokenService):
65
72
  }
66
73
  return self._jwt.generate_token(
67
74
  claims=claims,
68
- expiration_minutes=expiration_minutes,
75
+ expires_at=expires_at,
69
76
  )
70
77
 
71
78
 
@@ -74,7 +81,7 @@ class JwtAuthTokenService(IAuthTokenService):
74
81
  token: str,
75
82
  ) -> AccessTokenClaims:
76
83
 
77
- await self.__get_token_signatures()
84
+ await self._ensure_initialized()
78
85
 
79
86
  claims = self._jwt.validate_token(
80
87
  token=token,
@@ -84,12 +91,7 @@ class JwtAuthTokenService(IAuthTokenService):
84
91
  )
85
92
  )
86
93
 
87
- return AccessTokenClaims(
88
- id_legacy=claims["id_legacy"],
89
- id_enterprise=claims["id_enterprise"],
90
- aes_key=claims["aes_key"],
91
- exp=claims["exp"],
92
- )
94
+ return AccessTokenClaims.from_dict(claims)
93
95
 
94
96
 
95
97
  async def validate_action_token(
@@ -97,7 +99,7 @@ class JwtAuthTokenService(IAuthTokenService):
97
99
  token: str,
98
100
  ) -> ActionTokenClaims:
99
101
 
100
- await self.__get_token_signatures()
102
+ await self._ensure_initialized()
101
103
 
102
104
  claims = self._jwt.validate_token(
103
105
  token=token,
@@ -107,10 +109,7 @@ class JwtAuthTokenService(IAuthTokenService):
107
109
  )
108
110
  )
109
111
 
110
- return ActionTokenClaims(
111
- sub=claims["sub"],
112
- action=claims["action"],
113
- )
112
+ return ActionTokenClaims.from_dict(claims)
114
113
 
115
114
 
116
115
  async def validate_internal_access_token(
@@ -118,7 +117,7 @@ class JwtAuthTokenService(IAuthTokenService):
118
117
  token: str,
119
118
  ) -> UserTokenClaims:
120
119
 
121
- await self.__get_token_signatures()
120
+ await self._ensure_initialized()
122
121
 
123
122
  claims = self._jwt.validate_token(
124
123
  token=token,
@@ -130,18 +129,7 @@ class JwtAuthTokenService(IAuthTokenService):
130
129
  )
131
130
  )
132
131
 
133
- return UserTokenClaims(
134
- id_legacy=claims["id_legacy"],
135
- id_enterprise=claims["id_enterprise"],
136
- id_token=claims["id_token"],
137
- id_user=claims["id_user"],
138
- id_external_enterprise=claims["id_external_enterprise"],
139
- id_profile=claims["id_profile"],
140
- user_full_name=claims["user_full_name"],
141
- abbreviation=claims["abbreviation"],
142
- aes_key_auth=claims["aes_key_auth"],
143
- exp=claims["exp"],
144
- )
132
+ return UserTokenClaims.from_dict(claims)
145
133
 
146
134
 
147
135
  async def validate_token_for_refresh(
@@ -152,19 +140,8 @@ class JwtAuthTokenService(IAuthTokenService):
152
140
  Valida firma pero ignora exp.
153
141
  Usar exclusivamente para emitir un nuevo token cuando el anterior venció.
154
142
  """
155
- await self.__get_token_signatures()
143
+ await self._ensure_initialized()
156
144
 
157
145
  claims = self._jwt.get_claims_ignore_expiration(token)
158
146
 
159
- return UserTokenClaims(
160
- id_legacy=claims["id_legacy"],
161
- id_enterprise=claims["id_enterprise"],
162
- id_token=claims["id_token"],
163
- id_user=claims["id_user"],
164
- id_external_enterprise=claims["id_external_enterprise"],
165
- id_profile=claims["id_profile"],
166
- user_full_name=claims["user_full_name"],
167
- abbreviation=claims["abbreviation"],
168
- aes_key_auth=claims["aes_key_auth"],
169
- exp=claims["exp"],
170
- )
147
+ return UserTokenClaims.from_dict(claims)
@@ -1,47 +0,0 @@
1
- from typing import Dict, Any
2
- from dataclasses import dataclass, asdict
3
- from typing import Optional
4
-
5
- @dataclass(frozen=True, slots=True)
6
- class TokenValidationOptions:
7
- issuer: Optional[str] = None
8
- audience: Optional[str] = None
9
-
10
- validate_issuer: bool = True
11
- validate_audience: bool = True
12
-
13
- @dataclass(frozen=True, slots=True)
14
- class BaseClaims:
15
-
16
- def to_dict(self) -> Dict[str, Any]:
17
- return asdict(self)
18
-
19
- @dataclass(frozen=True, slots=True)
20
- class AccessTokenClaims(BaseClaims):
21
-
22
- id_legacy: int
23
- id_enterprise: int
24
- aes_key: str
25
- exp: Optional[int] = 0
26
-
27
-
28
- @dataclass(frozen=True, slots=True)
29
- class UserTokenClaims(BaseClaims):
30
-
31
- id_legacy: str
32
- id_enterprise: str
33
- id_token: str
34
- id_user: str
35
- id_external_enterprise: str
36
- id_profile: str
37
- user_full_name: str
38
- abbreviation: str
39
- aes_key_auth: str
40
- exp: Optional[int] = 0
41
-
42
-
43
- @dataclass(frozen=True, slots=True)
44
- class ActionTokenClaims(BaseClaims):
45
-
46
- sub: str
47
- action: Optional[str] = None
File without changes
File without changes