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.
- geek_cafe_saas_sdk/__init__.py +9 -0
- geek_cafe_saas_sdk/core/__init__.py +11 -0
- geek_cafe_saas_sdk/core/audit_mixin.py +33 -0
- geek_cafe_saas_sdk/core/error_codes.py +132 -0
- geek_cafe_saas_sdk/core/service_errors.py +19 -0
- geek_cafe_saas_sdk/core/service_result.py +121 -0
- geek_cafe_saas_sdk/decorators/__init__.py +64 -0
- geek_cafe_saas_sdk/decorators/auth.py +373 -0
- geek_cafe_saas_sdk/decorators/core.py +358 -0
- geek_cafe_saas_sdk/domains/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics.py +219 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics_summary.py +220 -0
- geek_cafe_saas_sdk/domains/analytics/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_service.py +232 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_summary_service.py +212 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_tally_service.py +610 -0
- geek_cafe_saas_sdk/domains/auth/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/auth/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/auth/models/permission.py +134 -0
- geek_cafe_saas_sdk/domains/auth/models/resource_permission.py +245 -0
- geek_cafe_saas_sdk/domains/auth/models/role.py +213 -0
- geek_cafe_saas_sdk/domains/auth/models/user.py +285 -0
- geek_cafe_saas_sdk/domains/auth/services/__init__.py +16 -0
- geek_cafe_saas_sdk/domains/auth/services/authorization_service.py +376 -0
- geek_cafe_saas_sdk/domains/auth/services/permission_registry.py +464 -0
- geek_cafe_saas_sdk/domains/auth/services/resource_permission_service.py +408 -0
- geek_cafe_saas_sdk/domains/auth/services/user_service.py +274 -0
- geek_cafe_saas_sdk/domains/communities/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/communities/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/models/community.py +326 -0
- geek_cafe_saas_sdk/domains/communities/models/community_member.py +227 -0
- geek_cafe_saas_sdk/domains/communities/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/services/community_member_service.py +412 -0
- geek_cafe_saas_sdk/domains/communities/services/community_service.py +479 -0
- geek_cafe_saas_sdk/domains/events/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/attendees/app.py +67 -0
- geek_cafe_saas_sdk/domains/events/handlers/cancel/app.py +66 -0
- geek_cafe_saas_sdk/domains/events/handlers/check_in/app.py +60 -0
- geek_cafe_saas_sdk/domains/events/handlers/create/app.py +93 -0
- geek_cafe_saas_sdk/domains/events/handlers/delete/app.py +42 -0
- geek_cafe_saas_sdk/domains/events/handlers/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/events/handlers/invite/app.py +98 -0
- geek_cafe_saas_sdk/domains/events/handlers/list/app.py +125 -0
- geek_cafe_saas_sdk/domains/events/handlers/publish/app.py +49 -0
- geek_cafe_saas_sdk/domains/events/handlers/rsvp/app.py +83 -0
- geek_cafe_saas_sdk/domains/events/handlers/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/events/models/__init__.py +3 -0
- geek_cafe_saas_sdk/domains/events/models/event.py +681 -0
- geek_cafe_saas_sdk/domains/events/models/event_attendee.py +324 -0
- geek_cafe_saas_sdk/domains/events/services/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/events/services/event_attendee_service.py +571 -0
- geek_cafe_saas_sdk/domains/events/services/event_service.py +684 -0
- geek_cafe_saas_sdk/domains/files/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/directory.py +258 -0
- geek_cafe_saas_sdk/domains/files/models/file.py +312 -0
- geek_cafe_saas_sdk/domains/files/models/file_share.py +268 -0
- geek_cafe_saas_sdk/domains/files/models/file_version.py +216 -0
- geek_cafe_saas_sdk/domains/files/services/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/services/directory_service.py +701 -0
- geek_cafe_saas_sdk/domains/files/services/file_share_service.py +663 -0
- geek_cafe_saas_sdk/domains/files/services/file_system_service.py +575 -0
- geek_cafe_saas_sdk/domains/files/services/file_version_service.py +739 -0
- geek_cafe_saas_sdk/domains/files/services/s3_file_service.py +501 -0
- geek_cafe_saas_sdk/domains/messaging/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/create/app.py +86 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/list/app.py +97 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/update/app.py +149 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/create/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/list/app.py +102 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/update/app.py +127 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/create/app.py +94 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/delete/app.py +66 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/get/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/list/app.py +95 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/update/app.py +156 -0
- geek_cafe_saas_sdk/domains/messaging/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py +337 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py +180 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_message.py +426 -0
- geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py +392 -0
- geek_cafe_saas_sdk/domains/messaging/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py +700 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py +491 -0
- geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +497 -0
- geek_cafe_saas_sdk/domains/tenancy/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/activate/app.py +52 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/active/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/cancel/app.py +55 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/list/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/record_payment/app.py +56 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/me/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/signup/app.py +61 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +440 -0
- geek_cafe_saas_sdk/domains/tenancy/models/tenant.py +258 -0
- geek_cafe_saas_sdk/domains/tenancy/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +557 -0
- geek_cafe_saas_sdk/domains/tenancy/services/tenant_service.py +575 -0
- geek_cafe_saas_sdk/domains/voting/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/create/app.py +128 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/list/app.py +38 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/summerize/README.md +3 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/voting/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/voting/models/vote.py +231 -0
- geek_cafe_saas_sdk/domains/voting/models/vote_summary.py +193 -0
- geek_cafe_saas_sdk/domains/voting/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_service.py +264 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_summary_service.py +198 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_tally_service.py +533 -0
- geek_cafe_saas_sdk/lambda_handlers/README.md +404 -0
- geek_cafe_saas_sdk/lambda_handlers/__init__.py +67 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/__init__.py +25 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/api_key_handler.py +129 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/authorized_secure_handler.py +218 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +185 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/handler_factory.py +256 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/public_handler.py +53 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/secure_handler.py +89 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/service_pool.py +94 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/create/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/delete/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/get/app.py +74 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/list/app.py +75 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/move/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/files/delete/app.py +121 -0
- geek_cafe_saas_sdk/lambda_handlers/files/download/app.py +187 -0
- geek_cafe_saas_sdk/lambda_handlers/files/get/app.py +127 -0
- geek_cafe_saas_sdk/lambda_handlers/files/list/app.py +108 -0
- geek_cafe_saas_sdk/lambda_handlers/files/share/app.py +83 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/list/app.py +84 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/revoke/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/files/update/app.py +143 -0
- geek_cafe_saas_sdk/lambda_handlers/files/upload/app.py +151 -0
- geek_cafe_saas_sdk/middleware/__init__.py +36 -0
- geek_cafe_saas_sdk/middleware/auth.py +85 -0
- geek_cafe_saas_sdk/middleware/authorization.py +523 -0
- geek_cafe_saas_sdk/middleware/cors.py +63 -0
- geek_cafe_saas_sdk/middleware/error_handling.py +114 -0
- geek_cafe_saas_sdk/middleware/validation.py +80 -0
- geek_cafe_saas_sdk/models/__init__.py +20 -0
- geek_cafe_saas_sdk/models/base_model.py +233 -0
- geek_cafe_saas_sdk/services/__init__.py +18 -0
- geek_cafe_saas_sdk/services/database_service.py +441 -0
- geek_cafe_saas_sdk/utilities/__init__.py +88 -0
- geek_cafe_saas_sdk/utilities/cognito_utility.py +568 -0
- geek_cafe_saas_sdk/utilities/custom_exceptions.py +183 -0
- geek_cafe_saas_sdk/utilities/datetime_utility.py +410 -0
- geek_cafe_saas_sdk/utilities/dictionary_utility.py +78 -0
- geek_cafe_saas_sdk/utilities/dynamodb_utils.py +151 -0
- geek_cafe_saas_sdk/utilities/environment_loader.py +149 -0
- geek_cafe_saas_sdk/utilities/environment_variables.py +228 -0
- geek_cafe_saas_sdk/utilities/http_body_parameters.py +44 -0
- geek_cafe_saas_sdk/utilities/http_path_parameters.py +60 -0
- geek_cafe_saas_sdk/utilities/http_status_code.py +63 -0
- geek_cafe_saas_sdk/utilities/jwt_utility.py +234 -0
- geek_cafe_saas_sdk/utilities/lambda_event_utility.py +776 -0
- geek_cafe_saas_sdk/utilities/logging_utility.py +64 -0
- geek_cafe_saas_sdk/utilities/message_query_helper.py +340 -0
- geek_cafe_saas_sdk/utilities/response.py +209 -0
- geek_cafe_saas_sdk/utilities/string_functions.py +180 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/METADATA +397 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/RECORD +194 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/WHEEL +4 -0
- 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
|