geek-cafe-saas-sdk 0.6.0__py3-none-any.whl

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.

Potentially problematic release.


This version of geek-cafe-saas-sdk might be problematic. Click here for more details.

Files changed (194) hide show
  1. geek_cafe_saas_sdk/__init__.py +9 -0
  2. geek_cafe_saas_sdk/core/__init__.py +11 -0
  3. geek_cafe_saas_sdk/core/audit_mixin.py +33 -0
  4. geek_cafe_saas_sdk/core/error_codes.py +132 -0
  5. geek_cafe_saas_sdk/core/service_errors.py +19 -0
  6. geek_cafe_saas_sdk/core/service_result.py +121 -0
  7. geek_cafe_saas_sdk/decorators/__init__.py +64 -0
  8. geek_cafe_saas_sdk/decorators/auth.py +373 -0
  9. geek_cafe_saas_sdk/decorators/core.py +358 -0
  10. geek_cafe_saas_sdk/domains/__init__.py +0 -0
  11. geek_cafe_saas_sdk/domains/analytics/__init__.py +0 -0
  12. geek_cafe_saas_sdk/domains/analytics/handlers/__init__.py +0 -0
  13. geek_cafe_saas_sdk/domains/analytics/models/__init__.py +9 -0
  14. geek_cafe_saas_sdk/domains/analytics/models/website_analytics.py +219 -0
  15. geek_cafe_saas_sdk/domains/analytics/models/website_analytics_summary.py +220 -0
  16. geek_cafe_saas_sdk/domains/analytics/services/__init__.py +11 -0
  17. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_service.py +232 -0
  18. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_summary_service.py +212 -0
  19. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_tally_service.py +610 -0
  20. geek_cafe_saas_sdk/domains/auth/__init__.py +0 -0
  21. geek_cafe_saas_sdk/domains/auth/handlers/__init__.py +0 -0
  22. geek_cafe_saas_sdk/domains/auth/handlers/users/create/app.py +41 -0
  23. geek_cafe_saas_sdk/domains/auth/handlers/users/delete/app.py +41 -0
  24. geek_cafe_saas_sdk/domains/auth/handlers/users/get/app.py +39 -0
  25. geek_cafe_saas_sdk/domains/auth/handlers/users/list/app.py +36 -0
  26. geek_cafe_saas_sdk/domains/auth/handlers/users/update/app.py +44 -0
  27. geek_cafe_saas_sdk/domains/auth/models/__init__.py +13 -0
  28. geek_cafe_saas_sdk/domains/auth/models/permission.py +134 -0
  29. geek_cafe_saas_sdk/domains/auth/models/resource_permission.py +245 -0
  30. geek_cafe_saas_sdk/domains/auth/models/role.py +213 -0
  31. geek_cafe_saas_sdk/domains/auth/models/user.py +285 -0
  32. geek_cafe_saas_sdk/domains/auth/services/__init__.py +16 -0
  33. geek_cafe_saas_sdk/domains/auth/services/authorization_service.py +376 -0
  34. geek_cafe_saas_sdk/domains/auth/services/permission_registry.py +464 -0
  35. geek_cafe_saas_sdk/domains/auth/services/resource_permission_service.py +408 -0
  36. geek_cafe_saas_sdk/domains/auth/services/user_service.py +274 -0
  37. geek_cafe_saas_sdk/domains/communities/__init__.py +0 -0
  38. geek_cafe_saas_sdk/domains/communities/handlers/__init__.py +0 -0
  39. geek_cafe_saas_sdk/domains/communities/handlers/communities/create/app.py +41 -0
  40. geek_cafe_saas_sdk/domains/communities/handlers/communities/delete/app.py +41 -0
  41. geek_cafe_saas_sdk/domains/communities/handlers/communities/get/app.py +39 -0
  42. geek_cafe_saas_sdk/domains/communities/handlers/communities/list/app.py +36 -0
  43. geek_cafe_saas_sdk/domains/communities/handlers/communities/update/app.py +44 -0
  44. geek_cafe_saas_sdk/domains/communities/models/__init__.py +6 -0
  45. geek_cafe_saas_sdk/domains/communities/models/community.py +326 -0
  46. geek_cafe_saas_sdk/domains/communities/models/community_member.py +227 -0
  47. geek_cafe_saas_sdk/domains/communities/services/__init__.py +6 -0
  48. geek_cafe_saas_sdk/domains/communities/services/community_member_service.py +412 -0
  49. geek_cafe_saas_sdk/domains/communities/services/community_service.py +479 -0
  50. geek_cafe_saas_sdk/domains/events/__init__.py +0 -0
  51. geek_cafe_saas_sdk/domains/events/handlers/__init__.py +0 -0
  52. geek_cafe_saas_sdk/domains/events/handlers/attendees/app.py +67 -0
  53. geek_cafe_saas_sdk/domains/events/handlers/cancel/app.py +66 -0
  54. geek_cafe_saas_sdk/domains/events/handlers/check_in/app.py +60 -0
  55. geek_cafe_saas_sdk/domains/events/handlers/create/app.py +93 -0
  56. geek_cafe_saas_sdk/domains/events/handlers/delete/app.py +42 -0
  57. geek_cafe_saas_sdk/domains/events/handlers/get/app.py +39 -0
  58. geek_cafe_saas_sdk/domains/events/handlers/invite/app.py +98 -0
  59. geek_cafe_saas_sdk/domains/events/handlers/list/app.py +125 -0
  60. geek_cafe_saas_sdk/domains/events/handlers/publish/app.py +49 -0
  61. geek_cafe_saas_sdk/domains/events/handlers/rsvp/app.py +83 -0
  62. geek_cafe_saas_sdk/domains/events/handlers/update/app.py +44 -0
  63. geek_cafe_saas_sdk/domains/events/models/__init__.py +3 -0
  64. geek_cafe_saas_sdk/domains/events/models/event.py +681 -0
  65. geek_cafe_saas_sdk/domains/events/models/event_attendee.py +324 -0
  66. geek_cafe_saas_sdk/domains/events/services/__init__.py +9 -0
  67. geek_cafe_saas_sdk/domains/events/services/event_attendee_service.py +571 -0
  68. geek_cafe_saas_sdk/domains/events/services/event_service.py +684 -0
  69. geek_cafe_saas_sdk/domains/files/__init__.py +0 -0
  70. geek_cafe_saas_sdk/domains/files/models/__init__.py +0 -0
  71. geek_cafe_saas_sdk/domains/files/models/directory.py +258 -0
  72. geek_cafe_saas_sdk/domains/files/models/file.py +312 -0
  73. geek_cafe_saas_sdk/domains/files/models/file_share.py +268 -0
  74. geek_cafe_saas_sdk/domains/files/models/file_version.py +216 -0
  75. geek_cafe_saas_sdk/domains/files/services/__init__.py +0 -0
  76. geek_cafe_saas_sdk/domains/files/services/directory_service.py +701 -0
  77. geek_cafe_saas_sdk/domains/files/services/file_share_service.py +663 -0
  78. geek_cafe_saas_sdk/domains/files/services/file_system_service.py +575 -0
  79. geek_cafe_saas_sdk/domains/files/services/file_version_service.py +739 -0
  80. geek_cafe_saas_sdk/domains/files/services/s3_file_service.py +501 -0
  81. geek_cafe_saas_sdk/domains/messaging/__init__.py +0 -0
  82. geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py +0 -0
  83. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/create/app.py +86 -0
  84. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/delete/app.py +65 -0
  85. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/get/app.py +64 -0
  86. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/list/app.py +97 -0
  87. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/update/app.py +149 -0
  88. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/create/app.py +67 -0
  89. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/delete/app.py +65 -0
  90. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/get/app.py +64 -0
  91. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/list/app.py +102 -0
  92. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/update/app.py +127 -0
  93. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/create/app.py +94 -0
  94. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/delete/app.py +66 -0
  95. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/get/app.py +67 -0
  96. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/list/app.py +95 -0
  97. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/update/app.py +156 -0
  98. geek_cafe_saas_sdk/domains/messaging/models/__init__.py +13 -0
  99. geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py +337 -0
  100. geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py +180 -0
  101. geek_cafe_saas_sdk/domains/messaging/models/chat_message.py +426 -0
  102. geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py +392 -0
  103. geek_cafe_saas_sdk/domains/messaging/services/__init__.py +11 -0
  104. geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py +700 -0
  105. geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py +491 -0
  106. geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +497 -0
  107. geek_cafe_saas_sdk/domains/tenancy/__init__.py +0 -0
  108. geek_cafe_saas_sdk/domains/tenancy/handlers/__init__.py +0 -0
  109. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/activate/app.py +52 -0
  110. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/active/app.py +37 -0
  111. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/cancel/app.py +55 -0
  112. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/get/app.py +39 -0
  113. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/list/app.py +44 -0
  114. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/record_payment/app.py +56 -0
  115. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/get/app.py +39 -0
  116. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/me/app.py +37 -0
  117. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/signup/app.py +61 -0
  118. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/update/app.py +44 -0
  119. geek_cafe_saas_sdk/domains/tenancy/models/__init__.py +6 -0
  120. geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +440 -0
  121. geek_cafe_saas_sdk/domains/tenancy/models/tenant.py +258 -0
  122. geek_cafe_saas_sdk/domains/tenancy/services/__init__.py +6 -0
  123. geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +557 -0
  124. geek_cafe_saas_sdk/domains/tenancy/services/tenant_service.py +575 -0
  125. geek_cafe_saas_sdk/domains/voting/__init__.py +0 -0
  126. geek_cafe_saas_sdk/domains/voting/handlers/__init__.py +0 -0
  127. geek_cafe_saas_sdk/domains/voting/handlers/votes/create/app.py +128 -0
  128. geek_cafe_saas_sdk/domains/voting/handlers/votes/delete/app.py +41 -0
  129. geek_cafe_saas_sdk/domains/voting/handlers/votes/get/app.py +39 -0
  130. geek_cafe_saas_sdk/domains/voting/handlers/votes/list/app.py +38 -0
  131. geek_cafe_saas_sdk/domains/voting/handlers/votes/summerize/README.md +3 -0
  132. geek_cafe_saas_sdk/domains/voting/handlers/votes/update/app.py +44 -0
  133. geek_cafe_saas_sdk/domains/voting/models/__init__.py +9 -0
  134. geek_cafe_saas_sdk/domains/voting/models/vote.py +231 -0
  135. geek_cafe_saas_sdk/domains/voting/models/vote_summary.py +193 -0
  136. geek_cafe_saas_sdk/domains/voting/services/__init__.py +11 -0
  137. geek_cafe_saas_sdk/domains/voting/services/vote_service.py +264 -0
  138. geek_cafe_saas_sdk/domains/voting/services/vote_summary_service.py +198 -0
  139. geek_cafe_saas_sdk/domains/voting/services/vote_tally_service.py +533 -0
  140. geek_cafe_saas_sdk/lambda_handlers/README.md +404 -0
  141. geek_cafe_saas_sdk/lambda_handlers/__init__.py +67 -0
  142. geek_cafe_saas_sdk/lambda_handlers/_base/__init__.py +25 -0
  143. geek_cafe_saas_sdk/lambda_handlers/_base/api_key_handler.py +129 -0
  144. geek_cafe_saas_sdk/lambda_handlers/_base/authorized_secure_handler.py +218 -0
  145. geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +185 -0
  146. geek_cafe_saas_sdk/lambda_handlers/_base/handler_factory.py +256 -0
  147. geek_cafe_saas_sdk/lambda_handlers/_base/public_handler.py +53 -0
  148. geek_cafe_saas_sdk/lambda_handlers/_base/secure_handler.py +89 -0
  149. geek_cafe_saas_sdk/lambda_handlers/_base/service_pool.py +94 -0
  150. geek_cafe_saas_sdk/lambda_handlers/directories/create/app.py +79 -0
  151. geek_cafe_saas_sdk/lambda_handlers/directories/delete/app.py +76 -0
  152. geek_cafe_saas_sdk/lambda_handlers/directories/get/app.py +74 -0
  153. geek_cafe_saas_sdk/lambda_handlers/directories/list/app.py +75 -0
  154. geek_cafe_saas_sdk/lambda_handlers/directories/move/app.py +79 -0
  155. geek_cafe_saas_sdk/lambda_handlers/files/delete/app.py +121 -0
  156. geek_cafe_saas_sdk/lambda_handlers/files/download/app.py +187 -0
  157. geek_cafe_saas_sdk/lambda_handlers/files/get/app.py +127 -0
  158. geek_cafe_saas_sdk/lambda_handlers/files/list/app.py +108 -0
  159. geek_cafe_saas_sdk/lambda_handlers/files/share/app.py +83 -0
  160. geek_cafe_saas_sdk/lambda_handlers/files/shares/list/app.py +84 -0
  161. geek_cafe_saas_sdk/lambda_handlers/files/shares/revoke/app.py +76 -0
  162. geek_cafe_saas_sdk/lambda_handlers/files/update/app.py +143 -0
  163. geek_cafe_saas_sdk/lambda_handlers/files/upload/app.py +151 -0
  164. geek_cafe_saas_sdk/middleware/__init__.py +36 -0
  165. geek_cafe_saas_sdk/middleware/auth.py +85 -0
  166. geek_cafe_saas_sdk/middleware/authorization.py +523 -0
  167. geek_cafe_saas_sdk/middleware/cors.py +63 -0
  168. geek_cafe_saas_sdk/middleware/error_handling.py +114 -0
  169. geek_cafe_saas_sdk/middleware/validation.py +80 -0
  170. geek_cafe_saas_sdk/models/__init__.py +20 -0
  171. geek_cafe_saas_sdk/models/base_model.py +233 -0
  172. geek_cafe_saas_sdk/services/__init__.py +18 -0
  173. geek_cafe_saas_sdk/services/database_service.py +441 -0
  174. geek_cafe_saas_sdk/utilities/__init__.py +88 -0
  175. geek_cafe_saas_sdk/utilities/cognito_utility.py +568 -0
  176. geek_cafe_saas_sdk/utilities/custom_exceptions.py +183 -0
  177. geek_cafe_saas_sdk/utilities/datetime_utility.py +410 -0
  178. geek_cafe_saas_sdk/utilities/dictionary_utility.py +78 -0
  179. geek_cafe_saas_sdk/utilities/dynamodb_utils.py +151 -0
  180. geek_cafe_saas_sdk/utilities/environment_loader.py +149 -0
  181. geek_cafe_saas_sdk/utilities/environment_variables.py +228 -0
  182. geek_cafe_saas_sdk/utilities/http_body_parameters.py +44 -0
  183. geek_cafe_saas_sdk/utilities/http_path_parameters.py +60 -0
  184. geek_cafe_saas_sdk/utilities/http_status_code.py +63 -0
  185. geek_cafe_saas_sdk/utilities/jwt_utility.py +234 -0
  186. geek_cafe_saas_sdk/utilities/lambda_event_utility.py +776 -0
  187. geek_cafe_saas_sdk/utilities/logging_utility.py +64 -0
  188. geek_cafe_saas_sdk/utilities/message_query_helper.py +340 -0
  189. geek_cafe_saas_sdk/utilities/response.py +209 -0
  190. geek_cafe_saas_sdk/utilities/string_functions.py +180 -0
  191. geek_cafe_saas_sdk-0.6.0.dist-info/METADATA +397 -0
  192. geek_cafe_saas_sdk-0.6.0.dist-info/RECORD +194 -0
  193. geek_cafe_saas_sdk-0.6.0.dist-info/WHEEL +4 -0
  194. geek_cafe_saas_sdk-0.6.0.dist-info/licenses/LICENSE +47 -0
@@ -0,0 +1,63 @@
1
+ """Geek Cafe SaaS Services Http Status Codes"""
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class HttpStatusCodes(Enum):
7
+ """Http Status Codes"""
8
+
9
+ HTTP_400_BAD_REQUEST = 400
10
+ """
11
+ The server cannot or will not process the request due to something that is perceived to be a client error
12
+ (e.g., malformed request syntax, invalid request message framing, or deceptive request routing)."""
13
+ HTTP_401_UNAUTHENTICATED = 401
14
+ """
15
+ Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated".
16
+ That is, the client must authenticate itself to get the requested response.
17
+ """
18
+ HTTP_403_FORBIDDEN = 403
19
+ """
20
+ The client does not have access rights to the content; that is, it is "unauthorized", so the server is refusing
21
+ to give the requested resource. Unlike 401 Unauthorized (which is technically UnAuthenticated);
22
+ here, the client's identity is known to the server.
23
+ """
24
+ HTTP_404_NOT_FOUND = 404
25
+ """
26
+ The server cannot find the requested resource. In the browser, this means the URL is not recognized.
27
+ In an API, this can also mean that the endpoint is valid but the resource itself does not exist.
28
+ Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from
29
+ an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.
30
+ """
31
+ HTTP_405_METHOD_NOT_ALLOWED = 405
32
+ """
33
+ The request method is known by the server but is not supported by the target resource.
34
+ For example, an API may not allow calling DELETE to remove a resource.
35
+ """
36
+ HTTP_406_NOT_ACCEPTABLE = 406
37
+ """
38
+ This response is sent when the web server, after performing server-driven content negotiation, doesn't find any
39
+ content that conforms to the criteria given by the user agent.
40
+ """
41
+ HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
42
+ """
43
+ This is similar to 401 Unauthorized but authentication is needed to be done by a proxy.
44
+ """
45
+ HTTP_408_REQUEST_TIMEOUT = 408
46
+ """
47
+ This response is sent on an idle connection by some servers, even without any previous request by the client.
48
+ It means that the server would like to shut down this unused connection. This response is used much more since
49
+ some browsers, like Chrome, Firefox 27+, or IE9, use HTTP pre-connection mechanisms to speed up surfing.
50
+ Also note that some servers merely shut down the connection without sending this message.
51
+ """
52
+
53
+ HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
54
+ """
55
+ The media format of the requested data is not supported by the server, so the server is rejecting the request.
56
+ """
57
+
58
+ HTTP_418_IM_A_TEAPOT = 418
59
+ """
60
+ The server refuses the attempt to brew coffee with a teapot.
61
+ """
62
+
63
+ HTTP_422_UNEXPECTED_OUTCOME = 422
@@ -0,0 +1,234 @@
1
+ """
2
+ JWT Token Utility for parsing JWT tokens.
3
+
4
+ This module provides utilities for parsing JWT tokens in Lambda functions.
5
+ Note: This is for parsing pre-validated tokens from API Gateway authorizers,
6
+ not for token validation/verification.
7
+ """
8
+
9
+ import json
10
+ import base64
11
+ from typing import Dict, Any, Optional
12
+ from aws_lambda_powertools import Logger
13
+
14
+ logger = Logger(__name__)
15
+
16
+
17
+ class JwtUtility:
18
+ """Utility class for parsing JWT tokens."""
19
+
20
+ @staticmethod
21
+ def parse_jwt_payload(token: str) -> Optional[Dict[str, Any]]:
22
+ """
23
+ Parse the payload from a JWT token without verification.
24
+
25
+ This method extracts and decodes the payload section of a JWT token.
26
+ It does NOT verify the token signature - use this only for tokens
27
+ that have already been validated by API Gateway authorizers.
28
+
29
+ Args:
30
+ token: The JWT token string (with or without 'Bearer ' prefix)
31
+
32
+ Returns:
33
+ The decoded payload as a dictionary, or None if parsing fails
34
+
35
+ Raises:
36
+ ValueError: If the token format is invalid
37
+ """
38
+ try:
39
+ # Remove 'Bearer ' prefix if present
40
+ if token.startswith('Bearer '):
41
+ token = token[7:]
42
+ elif token.startswith('bearer '):
43
+ token = token[7:]
44
+
45
+ # JWT tokens have 3 parts separated by dots: header.payload.signature
46
+ parts = token.split('.')
47
+ if len(parts) != 3:
48
+ raise ValueError(f"Invalid JWT format: expected 3 parts, got {len(parts)}")
49
+
50
+ # Get the payload (second part)
51
+ payload_part = parts[1]
52
+
53
+ # Add padding if needed (base64 requires length to be multiple of 4)
54
+ payload_part = JwtUtility._add_base64_padding(payload_part)
55
+
56
+ # Decode the base64-encoded payload
57
+ decoded_bytes = base64.urlsafe_b64decode(payload_part)
58
+ decoded_str = decoded_bytes.decode('utf-8')
59
+
60
+ # Parse the JSON payload
61
+ payload = json.loads(decoded_str)
62
+
63
+ logger.debug(f"Successfully parsed JWT payload with keys: {list(payload.keys())}")
64
+ return payload
65
+
66
+ except Exception as e:
67
+ logger.warning(f"Failed to parse JWT token: {str(e)}")
68
+ return None
69
+
70
+ @staticmethod
71
+ def parse_jwt_header(token: str) -> Optional[Dict[str, Any]]:
72
+ """
73
+ Parse the header from a JWT token.
74
+
75
+ Args:
76
+ token: The JWT token string (with or without 'Bearer ' prefix)
77
+
78
+ Returns:
79
+ The decoded header as a dictionary, or None if parsing fails
80
+ """
81
+ try:
82
+ # Remove 'Bearer ' prefix if present
83
+ if token.startswith('Bearer '):
84
+ token = token[7:]
85
+ elif token.startswith('bearer '):
86
+ token = token[7:]
87
+
88
+ # JWT tokens have 3 parts separated by dots: header.payload.signature
89
+ parts = token.split('.')
90
+ if len(parts) != 3:
91
+ raise ValueError(f"Invalid JWT format: expected 3 parts, got {len(parts)}")
92
+
93
+ # Get the header (first part)
94
+ header_part = parts[0]
95
+
96
+ # Add padding if needed
97
+ header_part = JwtUtility._add_base64_padding(header_part)
98
+
99
+ # Decode the base64-encoded header
100
+ decoded_bytes = base64.urlsafe_b64decode(header_part)
101
+ decoded_str = decoded_bytes.decode('utf-8')
102
+
103
+ # Parse the JSON header
104
+ header = json.loads(decoded_str)
105
+
106
+ logger.debug(f"Successfully parsed JWT header: {header}")
107
+ return header
108
+
109
+ except Exception as e:
110
+ logger.warning(f"Failed to parse JWT header: {str(e)}")
111
+ return None
112
+
113
+ @staticmethod
114
+ def get_claim_from_token(token: str, claim_key: str) -> Optional[str]:
115
+ """
116
+ Extract a specific claim from a JWT token.
117
+
118
+ Args:
119
+ token: The JWT token string
120
+ claim_key: The claim key to extract (e.g., 'sub', 'email', 'custom:user_id')
121
+
122
+ Returns:
123
+ The claim value as a string, or None if not found
124
+ """
125
+ payload = JwtUtility.parse_jwt_payload(token)
126
+ if not payload:
127
+ return None
128
+
129
+ claim_value = payload.get(claim_key)
130
+ if claim_value is not None:
131
+ return str(claim_value)
132
+
133
+ return None
134
+
135
+ @staticmethod
136
+ def _add_base64_padding(encoded_string: str) -> str:
137
+ """
138
+ Add padding to a base64-encoded string if needed.
139
+
140
+ Base64 strings must have a length that's a multiple of 4.
141
+ JWT tokens often omit the padding characters.
142
+
143
+ Args:
144
+ encoded_string: The base64-encoded string
145
+
146
+ Returns:
147
+ The padded base64 string
148
+ """
149
+ # Calculate how many padding characters we need
150
+ padding_needed = 4 - (len(encoded_string) % 4)
151
+
152
+ # Add padding if needed (but not if it's already a multiple of 4)
153
+ if padding_needed != 4:
154
+ encoded_string += '=' * padding_needed
155
+
156
+ return encoded_string
157
+
158
+ @staticmethod
159
+ def is_token_expired(token: str) -> Optional[bool]:
160
+ """
161
+ Check if a JWT token is expired based on the 'exp' claim.
162
+
163
+ Args:
164
+ token: The JWT token string
165
+
166
+ Returns:
167
+ True if expired, False if not expired, None if exp claim not found or parsing failed
168
+ """
169
+ import time
170
+
171
+ payload = JwtUtility.parse_jwt_payload(token)
172
+ if not payload:
173
+ return None
174
+
175
+ exp_claim = payload.get('exp')
176
+ if exp_claim is None:
177
+ return None
178
+
179
+ try:
180
+ # 'exp' claim is typically a Unix timestamp
181
+ exp_timestamp = int(exp_claim)
182
+ current_timestamp = int(time.time())
183
+
184
+ return current_timestamp > exp_timestamp
185
+
186
+ except (ValueError, TypeError):
187
+ logger.warning(f"Invalid 'exp' claim format: {exp_claim}")
188
+ return None
189
+
190
+ @staticmethod
191
+ def get_token_info(token: str) -> Dict[str, Any]:
192
+ """
193
+ Get comprehensive information about a JWT token.
194
+
195
+ Args:
196
+ token: The JWT token string
197
+
198
+ Returns:
199
+ Dictionary containing token information including header, payload, and metadata
200
+ """
201
+ info = {
202
+ 'valid': False,
203
+ 'header': None,
204
+ 'payload': None,
205
+ 'expired': None,
206
+ 'claims': {},
207
+ }
208
+
209
+ try:
210
+ # Parse header and payload
211
+ header = JwtUtility.parse_jwt_header(token)
212
+ payload = JwtUtility.parse_jwt_payload(token)
213
+
214
+ if header and payload:
215
+ info['valid'] = True
216
+ info['header'] = header
217
+ info['payload'] = payload
218
+ info['expired'] = JwtUtility.is_token_expired(token)
219
+
220
+ # Extract common claims
221
+ common_claims = [
222
+ 'sub', 'iss', 'aud', 'exp', 'iat', 'nbf', 'jti',
223
+ 'email', 'name', 'given_name', 'family_name',
224
+ 'custom:user_id', 'custom:tenant_id', 'custom:user_roles'
225
+ ]
226
+
227
+ for claim in common_claims:
228
+ if claim in payload:
229
+ info['claims'][claim] = payload[claim]
230
+
231
+ except Exception as e:
232
+ logger.error(f"Error getting token info: {str(e)}")
233
+
234
+ return info