quantum-framework 0.9.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.
Files changed (262) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/commands/__init__.py +15 -0
  4. quantum/cli/commands/build.py +417 -0
  5. quantum/cli/commands/dev.py +185 -0
  6. quantum/cli/commands/docs.py +329 -0
  7. quantum/cli/commands/lint.py +523 -0
  8. quantum/cli/commands/migrate.py +527 -0
  9. quantum/cli/commands/new.py +622 -0
  10. quantum/cli/commands/serve.py +190 -0
  11. quantum/cli/commands/test.py +193 -0
  12. quantum/cli/deploy.py +810 -0
  13. quantum/cli/hot_reload.py +951 -0
  14. quantum/cli/jobs.py +356 -0
  15. quantum/cli/mq.py +582 -0
  16. quantum/cli/pkg.py +390 -0
  17. quantum/cli/runner.py +547 -0
  18. quantum/cli/server_process.py +159 -0
  19. quantum/cli/utils.py +334 -0
  20. quantum/compiler/__init__.py +30 -0
  21. quantum/compiler/base_generator.py +367 -0
  22. quantum/compiler/cli.py +295 -0
  23. quantum/compiler/expression_transformer.py +444 -0
  24. quantum/compiler/javascript/__init__.py +10 -0
  25. quantum/compiler/javascript/generator.py +659 -0
  26. quantum/compiler/optimizer.py +270 -0
  27. quantum/compiler/python/__init__.py +10 -0
  28. quantum/compiler/python/generator.py +883 -0
  29. quantum/compiler/python/runtime.py +863 -0
  30. quantum/compiler/transpiler.py +330 -0
  31. quantum/core/__init__.py +3 -0
  32. quantum/core/ast_nodes.py +2611 -0
  33. quantum/core/expression_diagnostics.py +87 -0
  34. quantum/core/expression_stdlib.py +148 -0
  35. quantum/core/expressions.py +591 -0
  36. quantum/core/features/agents/src/__init__.py +30 -0
  37. quantum/core/features/agents/src/ast_node.py +540 -0
  38. quantum/core/features/conditionals/src/__init__.py +8 -0
  39. quantum/core/features/conditionals/src/ast_node.py +68 -0
  40. quantum/core/features/data_fetching/src/__init__.py +21 -0
  41. quantum/core/features/data_fetching/src/ast_node.py +312 -0
  42. quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
  43. quantum/core/features/data_fetching/src/html_adapter.py +474 -0
  44. quantum/core/features/data_fetching/src/parser.py +225 -0
  45. quantum/core/features/data_import/src/__init__.py +0 -0
  46. quantum/core/features/data_import/src/ast_node.py +291 -0
  47. quantum/core/features/data_import/src/runtime.py +538 -0
  48. quantum/core/features/dump/src/__init__.py +12 -0
  49. quantum/core/features/dump/src/ast_node.py +106 -0
  50. quantum/core/features/dump/src/parser.py +61 -0
  51. quantum/core/features/dump/src/runtime.py +246 -0
  52. quantum/core/features/functions/src/__init__.py +8 -0
  53. quantum/core/features/functions/src/ast_node.py +149 -0
  54. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  55. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  56. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  57. quantum/core/features/invocation/src/__init__.py +0 -0
  58. quantum/core/features/invocation/src/ast_node.py +146 -0
  59. quantum/core/features/invocation/src/runtime.py +327 -0
  60. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  61. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  62. quantum/core/features/knowledge_base/src/parser.py +82 -0
  63. quantum/core/features/logging/src/__init__.py +12 -0
  64. quantum/core/features/logging/src/ast_node.py +111 -0
  65. quantum/core/features/logging/src/parser.py +50 -0
  66. quantum/core/features/logging/src/runtime.py +190 -0
  67. quantum/core/features/loops/src/__init__.py +8 -0
  68. quantum/core/features/loops/src/ast_node.py +60 -0
  69. quantum/core/features/query/src/__init__.py +0 -0
  70. quantum/core/features/query/src/database_service.py +322 -0
  71. quantum/core/features/query/src/query_validators.py +20 -0
  72. quantum/core/features/state_management/src/__init__.py +11 -0
  73. quantum/core/features/state_management/src/ast_node.py +228 -0
  74. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  75. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  76. quantum/core/features/terminal_engine/src/parser.py +361 -0
  77. quantum/core/features/testing_engine/src/__init__.py +41 -0
  78. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  79. quantum/core/features/testing_engine/src/parser.py +604 -0
  80. quantum/core/features/theming/src/__init__.py +48 -0
  81. quantum/core/features/theming/src/ast_node.py +137 -0
  82. quantum/core/features/theming/src/presets.py +405 -0
  83. quantum/core/features/ui_engine/src/__init__.py +20 -0
  84. quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
  85. quantum/core/features/ui_engine/src/parser.py +1106 -0
  86. quantum/core/features/websocket/src/__init__.py +24 -0
  87. quantum/core/features/websocket/src/ast_node.py +247 -0
  88. quantum/core/html_compat.py +299 -0
  89. quantum/core/parser.py +1235 -0
  90. quantum/core/parser_registry.py +213 -0
  91. quantum/core/parsers/__init__.py +76 -0
  92. quantum/core/parsers/ai/__init__.py +12 -0
  93. quantum/core/parsers/ai/agent_parser.py +106 -0
  94. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  95. quantum/core/parsers/ai/llm_parser.py +78 -0
  96. quantum/core/parsers/ai/team_parser.py +88 -0
  97. quantum/core/parsers/base.py +322 -0
  98. quantum/core/parsers/composition/__init__.py +10 -0
  99. quantum/core/parsers/composition/import_parser.py +50 -0
  100. quantum/core/parsers/composition/slot_parser.py +48 -0
  101. quantum/core/parsers/control_flow/__init__.py +11 -0
  102. quantum/core/parsers/control_flow/if_parser.py +68 -0
  103. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  104. quantum/core/parsers/control_flow/set_parser.py +89 -0
  105. quantum/core/parsers/data/__init__.py +12 -0
  106. quantum/core/parsers/data/data_parser.py +217 -0
  107. quantum/core/parsers/data/invoke_parser.py +116 -0
  108. quantum/core/parsers/data/query_parser.py +188 -0
  109. quantum/core/parsers/data/transaction_parser.py +77 -0
  110. quantum/core/parsers/events/__init__.py +9 -0
  111. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  112. quantum/core/parsers/forms/__init__.py +11 -0
  113. quantum/core/parsers/forms/action_parser.py +54 -0
  114. quantum/core/parsers/forms/flash_parser.py +34 -0
  115. quantum/core/parsers/forms/redirect_parser.py +33 -0
  116. quantum/core/parsers/functions/__init__.py +11 -0
  117. quantum/core/parsers/functions/function_parser.py +137 -0
  118. quantum/core/parsers/functions/param_parser.py +66 -0
  119. quantum/core/parsers/functions/return_parser.py +31 -0
  120. quantum/core/parsers/html/__init__.py +10 -0
  121. quantum/core/parsers/html/component_call_parser.py +130 -0
  122. quantum/core/parsers/html/html_parser.py +115 -0
  123. quantum/core/parsers/jobs/__init__.py +11 -0
  124. quantum/core/parsers/jobs/job_parser.py +71 -0
  125. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  126. quantum/core/parsers/jobs/thread_parser.py +57 -0
  127. quantum/core/parsers/messaging/__init__.py +17 -0
  128. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  130. quantum/core/parsers/messaging/message_parser.py +114 -0
  131. quantum/core/parsers/messaging/queue_parser.py +61 -0
  132. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  133. quantum/core/parsers/persistence/__init__.py +9 -0
  134. quantum/core/parsers/persistence/persist_parser.py +64 -0
  135. quantum/core/parsers/routing/__init__.py +9 -0
  136. quantum/core/parsers/routing/route_parser.py +41 -0
  137. quantum/core/parsers/scripting/__init__.py +12 -0
  138. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  139. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  140. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  141. quantum/core/parsers/scripting/python_parser.py +49 -0
  142. quantum/core/parsers/services/__init__.py +12 -0
  143. quantum/core/parsers/services/dump_parser.py +52 -0
  144. quantum/core/parsers/services/file_parser.py +48 -0
  145. quantum/core/parsers/services/log_parser.py +46 -0
  146. quantum/core/parsers/services/mail_parser.py +65 -0
  147. quantum/core/tiers.py +82 -0
  148. quantum/packages/__init__.py +28 -0
  149. quantum/packages/manager.py +413 -0
  150. quantum/packages/manifest.py +351 -0
  151. quantum/packages/registry.py +399 -0
  152. quantum/packages/resolver.py +336 -0
  153. quantum/plugins/__init__.py +33 -0
  154. quantum/plugins/hooks.py +329 -0
  155. quantum/plugins/loader.py +479 -0
  156. quantum/plugins/manifest.py +336 -0
  157. quantum/plugins/registry.py +371 -0
  158. quantum/runtime/__init__.py +28 -0
  159. quantum/runtime/action_handler.py +443 -0
  160. quantum/runtime/adapters/__init__.py +88 -0
  161. quantum/runtime/adapters/memory_adapter.py +690 -0
  162. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  163. quantum/runtime/adapters/redis_adapter.py +582 -0
  164. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  165. quantum/runtime/agent_service.py +1133 -0
  166. quantum/runtime/api_server.py +86 -0
  167. quantum/runtime/ast_cache.py +506 -0
  168. quantum/runtime/auth_service.py +267 -0
  169. quantum/runtime/component.py +990 -0
  170. quantum/runtime/component_composer.py +319 -0
  171. quantum/runtime/component_resolver.py +174 -0
  172. quantum/runtime/database_service.py +598 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +286 -0
  176. quantum/runtime/executor_registry.py +171 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +262 -0
  184. quantum/runtime/executors/control_flow/__init__.py +11 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +93 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +307 -0
  187. quantum/runtime/executors/control_flow/set_executor.py +412 -0
  188. quantum/runtime/executors/data/__init__.py +12 -0
  189. quantum/runtime/executors/data/data_executor.py +145 -0
  190. quantum/runtime/executors/data/invoke_executor.py +176 -0
  191. quantum/runtime/executors/data/query_executor.py +256 -0
  192. quantum/runtime/executors/data/transaction_executor.py +91 -0
  193. quantum/runtime/executors/jobs/__init__.py +11 -0
  194. quantum/runtime/executors/jobs/job_executor.py +190 -0
  195. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  196. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  197. quantum/runtime/executors/messaging/__init__.py +17 -0
  198. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  199. quantum/runtime/executors/messaging/message_executor.py +174 -0
  200. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  201. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  202. quantum/runtime/executors/scripting/__init__.py +11 -0
  203. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  204. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  205. quantum/runtime/executors/scripting/python_executor.py +249 -0
  206. quantum/runtime/executors/services/__init__.py +12 -0
  207. quantum/runtime/executors/services/dump_executor.py +72 -0
  208. quantum/runtime/executors/services/file_executor.py +89 -0
  209. quantum/runtime/executors/services/log_executor.py +77 -0
  210. quantum/runtime/executors/services/mail_executor.py +81 -0
  211. quantum/runtime/expression_cache.py +498 -0
  212. quantum/runtime/file_upload_service.py +326 -0
  213. quantum/runtime/function_registry.py +118 -0
  214. quantum/runtime/game_builder.py +166 -0
  215. quantum/runtime/game_code_generator.py +2371 -0
  216. quantum/runtime/game_templates.py +2006 -0
  217. quantum/runtime/godot_code_generator.py +4681 -0
  218. quantum/runtime/godot_templates.py +1449 -0
  219. quantum/runtime/job_executor.py +1599 -0
  220. quantum/runtime/knowledge_service.py +500 -0
  221. quantum/runtime/llm_cache.py +100 -0
  222. quantum/runtime/llm_providers.py +704 -0
  223. quantum/runtime/llm_service.py +287 -0
  224. quantum/runtime/logging_setup.py +140 -0
  225. quantum/runtime/message_broker.py +364 -0
  226. quantum/runtime/message_queue_service.py +571 -0
  227. quantum/runtime/param_validation.py +184 -0
  228. quantum/runtime/pypy_compat.py +315 -0
  229. quantum/runtime/python_bridge.py +698 -0
  230. quantum/runtime/query_validators.py +304 -0
  231. quantum/runtime/renderer.py +733 -0
  232. quantum/runtime/service_container.py +444 -0
  233. quantum/runtime/terminal_builder.py +76 -0
  234. quantum/runtime/terminal_code_generator.py +607 -0
  235. quantum/runtime/terminal_templates.py +243 -0
  236. quantum/runtime/testing_builder.py +77 -0
  237. quantum/runtime/testing_code_generator.py +833 -0
  238. quantum/runtime/testing_templates.py +85 -0
  239. quantum/runtime/ui_builder.py +188 -0
  240. quantum/runtime/ui_desktop_adapter.py +1730 -0
  241. quantum/runtime/ui_desktop_templates.py +307 -0
  242. quantum/runtime/ui_html_adapter.py +2691 -0
  243. quantum/runtime/ui_html_templates.py +2297 -0
  244. quantum/runtime/ui_mobile_adapter.py +1832 -0
  245. quantum/runtime/ui_mobile_templates.py +1003 -0
  246. quantum/runtime/ui_textual_adapter.py +1866 -0
  247. quantum/runtime/ui_textual_templates.py +45 -0
  248. quantum/runtime/ui_tokens.py +465 -0
  249. quantum/runtime/ui_validator.py +365 -0
  250. quantum/runtime/validators.py +256 -0
  251. quantum/runtime/web_server.py +1766 -0
  252. quantum/runtime/websocket_adapter.py +501 -0
  253. quantum/runtime/websocket_service.py +585 -0
  254. quantum/runtime/websocket_transport.py +289 -0
  255. quantum/runtime/wsgi.py +101 -0
  256. quantum/utils/__init__.py +1 -0
  257. quantum_framework-0.9.0.dist-info/METADATA +244 -0
  258. quantum_framework-0.9.0.dist-info/RECORD +262 -0
  259. quantum_framework-0.9.0.dist-info/WHEEL +5 -0
  260. quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
  261. quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
  262. quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,267 @@
1
+ """
2
+ Phase G: Authentication & Security Service
3
+
4
+ Provides password hashing, authentication, and RBAC functionality.
5
+ """
6
+
7
+ import bcrypt
8
+ from typing import Optional, List, Dict, Any
9
+ from datetime import datetime, timedelta
10
+
11
+
12
+ class AuthService:
13
+ """Authentication and security service for Quantum framework"""
14
+
15
+ @staticmethod
16
+ def hash_password(password: str) -> str:
17
+ """
18
+ Hash a password using bcrypt
19
+
20
+ Args:
21
+ password: Plain text password
22
+
23
+ Returns:
24
+ Hashed password (bcrypt hash)
25
+ """
26
+ if not password:
27
+ raise ValueError("Password cannot be empty")
28
+
29
+ # Generate salt and hash password
30
+ salt = bcrypt.gensalt()
31
+ hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
32
+
33
+ return hashed.decode('utf-8')
34
+
35
+ @staticmethod
36
+ def verify_password(password: str, hashed: str) -> bool:
37
+ """
38
+ Verify a password against a bcrypt hash
39
+
40
+ Args:
41
+ password: Plain text password to verify
42
+ hashed: Bcrypt hash to check against
43
+
44
+ Returns:
45
+ True if password matches, False otherwise
46
+ """
47
+ if not password or not hashed:
48
+ return False
49
+
50
+ try:
51
+ return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))
52
+ except Exception:
53
+ return False
54
+
55
+ @staticmethod
56
+ def is_authenticated(session_data: Dict[str, Any]) -> bool:
57
+ """
58
+ Check if user is authenticated based on session data
59
+
60
+ Args:
61
+ session_data: Session dictionary
62
+
63
+ Returns:
64
+ True if authenticated, False otherwise
65
+ """
66
+ return AuthService._is_true(session_data.get('authenticated'))
67
+
68
+ # The set of values that count as "yes". Deliberately CLOSED: anything not
69
+ # listed is false, so this stays fail-closed. In particular '' , None, 0,
70
+ # 'false', 'no' and any unrecognised string are NOT authenticated.
71
+ _TRUTHY = frozenset({'true', '1', 'yes', 'on'})
72
+
73
+ @staticmethod
74
+ def _is_true(value: Any) -> bool:
75
+ """Whether a session flag means yes.
76
+
77
+ This used to be `value is True`, requiring the Python boolean. But a
78
+ .q attribute is a string: the framework's own components/login.q line
79
+ 19 writes
80
+
81
+ <q:set name="session.authenticated" value="true" />
82
+
83
+ which stores the STRING "true" unless the author remembered
84
+ type="boolean". "true" is not True, so is_authenticated returned False
85
+ for a user who had just logged in successfully, and require_auth
86
+ bounced them back to /login forever.
87
+
88
+ Same root cause as the q:set arithmetic bug: the engine stores strings
89
+ and the consumer demanded a strict type. Accepting the written forms
90
+ fixes the login without widening what counts as authenticated —
91
+ the list is closed and everything else is false.
92
+ """
93
+ if isinstance(value, bool):
94
+ return value
95
+ if isinstance(value, str):
96
+ return value.strip().lower() in AuthService._TRUTHY
97
+ return value is True
98
+
99
+ @staticmethod
100
+ def get_user_id(session_data: Dict[str, Any]) -> Optional[int]:
101
+ """
102
+ Get authenticated user ID from session
103
+
104
+ Args:
105
+ session_data: Session dictionary
106
+
107
+ Returns:
108
+ User ID if authenticated, None otherwise
109
+ """
110
+ if not AuthService.is_authenticated(session_data):
111
+ return None
112
+
113
+ return session_data.get('userId')
114
+
115
+ @staticmethod
116
+ def get_user_role(session_data: Dict[str, Any]) -> Optional[str]:
117
+ """
118
+ Get authenticated user role from session
119
+
120
+ Args:
121
+ session_data: Session dictionary
122
+
123
+ Returns:
124
+ User role if authenticated, None otherwise
125
+ """
126
+ if not AuthService.is_authenticated(session_data):
127
+ return None
128
+
129
+ return session_data.get('userRole')
130
+
131
+ @staticmethod
132
+ def has_permission(session_data: Dict[str, Any], required_permission: str) -> bool:
133
+ """
134
+ Check if user has a specific permission
135
+
136
+ Args:
137
+ session_data: Session dictionary
138
+ required_permission: Permission to check (e.g., "admin", "user", "editor")
139
+
140
+ Returns:
141
+ True if user has permission, False otherwise
142
+ """
143
+ if not AuthService.is_authenticated(session_data):
144
+ return False
145
+
146
+ user_permissions = session_data.get('permissions', [])
147
+
148
+ # If permissions is a string, convert to list
149
+ if isinstance(user_permissions, str):
150
+ user_permissions = [user_permissions]
151
+
152
+ return required_permission in user_permissions
153
+
154
+ @staticmethod
155
+ def has_role(session_data: Dict[str, Any], required_role: str) -> bool:
156
+ """
157
+ Check if user has a specific role
158
+
159
+ Args:
160
+ session_data: Session dictionary
161
+ required_role: Role to check (e.g., "admin", "user", "editor")
162
+
163
+ Returns:
164
+ True if user has role, False otherwise
165
+ """
166
+ if not AuthService.is_authenticated(session_data):
167
+ return False
168
+
169
+ user_role = session_data.get('userRole', '')
170
+
171
+ # Support multiple roles separated by comma
172
+ if ',' in required_role:
173
+ required_roles = [r.strip() for r in required_role.split(',')]
174
+ return user_role in required_roles
175
+
176
+ return user_role == required_role
177
+
178
+ @staticmethod
179
+ def login(session_data: Dict[str, Any], user_id: int, user_name: str,
180
+ user_role: str = "user", permissions: List[str] = None,
181
+ remember_me: bool = False) -> Dict[str, Any]:
182
+ """
183
+ Login user and set session data
184
+
185
+ Args:
186
+ session_data: Session dictionary to update
187
+ user_id: User ID
188
+ user_name: User name
189
+ user_role: User role (default: "user")
190
+ permissions: List of permissions (optional)
191
+ remember_me: Whether to remember user (extended session)
192
+
193
+ Returns:
194
+ Updated session data
195
+ """
196
+ session_data['authenticated'] = True
197
+ session_data['userId'] = user_id
198
+ session_data['userName'] = user_name
199
+ session_data['userRole'] = user_role
200
+ session_data['loginTime'] = datetime.now().isoformat()
201
+
202
+ if permissions:
203
+ session_data['permissions'] = permissions
204
+
205
+ if remember_me:
206
+ # Set extended session expiry (30 days)
207
+ session_data['rememberMe'] = True
208
+ session_data['sessionExpiry'] = (datetime.now() + timedelta(days=30)).isoformat()
209
+ else:
210
+ # Default session expiry (24 hours)
211
+ session_data['sessionExpiry'] = (datetime.now() + timedelta(hours=24)).isoformat()
212
+
213
+ return session_data
214
+
215
+ @staticmethod
216
+ def logout(session_data: Dict[str, Any]) -> Dict[str, Any]:
217
+ """
218
+ Logout user and clear session data
219
+
220
+ Args:
221
+ session_data: Session dictionary to clear
222
+
223
+ Returns:
224
+ Cleared session data
225
+ """
226
+ # Clear all authentication-related session data
227
+ keys_to_remove = [
228
+ 'authenticated', 'userId', 'userName', 'userRole',
229
+ 'loginTime', 'permissions', 'rememberMe', 'sessionExpiry'
230
+ ]
231
+
232
+ for key in keys_to_remove:
233
+ session_data.pop(key, None)
234
+
235
+ session_data['logoutTime'] = datetime.now().isoformat()
236
+
237
+ return session_data
238
+
239
+ @staticmethod
240
+ def is_session_expired(session_data: Dict[str, Any]) -> bool:
241
+ """
242
+ Check if session has expired
243
+
244
+ Args:
245
+ session_data: Session dictionary
246
+
247
+ Returns:
248
+ True if session expired, False otherwise
249
+ """
250
+ if not AuthService.is_authenticated(session_data):
251
+ return True
252
+
253
+ expiry_str = session_data.get('sessionExpiry')
254
+ if not expiry_str:
255
+ # No expiry set - consider expired for safety
256
+ return True
257
+
258
+ try:
259
+ expiry_time = datetime.fromisoformat(expiry_str)
260
+ return datetime.now() > expiry_time
261
+ except Exception:
262
+ return True
263
+
264
+
265
+ class AuthorizationError(Exception):
266
+ """Raised when user is not authorized to access a resource"""
267
+ pass