legate 0.1.0

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 (317) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +345 -0
  4. data/bin/legate +13 -0
  5. data/examples/00_quickstart.rb +51 -0
  6. data/examples/01_simple_agent.rb +105 -0
  7. data/examples/02_multi_tool_agent.rb +140 -0
  8. data/examples/03_custom_tool.rb +93 -0
  9. data/examples/04_agent_instructions.rb +84 -0
  10. data/examples/05_state_and_sessions.rb +91 -0
  11. data/examples/06_callbacks.rb +186 -0
  12. data/examples/07_async_jobs.rb +112 -0
  13. data/examples/08_loop_agent.rb +197 -0
  14. data/examples/09_sequential_workflow.rb +40 -0
  15. data/examples/10_parallel_workflow.rb +34 -0
  16. data/examples/11_agent_delegation.rb +24 -0
  17. data/examples/12_http_client_tool.rb +156 -0
  18. data/examples/13_authentication.rb +220 -0
  19. data/examples/14_mcp_client.rb +154 -0
  20. data/examples/15_mcp_server.rb +79 -0
  21. data/examples/16_webhooks.rb +91 -0
  22. data/examples/README_sequential_agents.md +164 -0
  23. data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
  24. data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
  25. data/examples/advanced/auth/excon_middleware.rb +317 -0
  26. data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
  27. data/examples/advanced/auth/fiber_auth_example.rb +281 -0
  28. data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
  29. data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
  30. data/examples/advanced/auth/oauth2_auth.rb +419 -0
  31. data/examples/advanced/auth/oidc_auth.rb +514 -0
  32. data/examples/advanced/auth/openweather_api.rb +251 -0
  33. data/examples/advanced/auth/openweather_tool.rb +153 -0
  34. data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
  35. data/examples/advanced/auth/service_account.rb +135 -0
  36. data/examples/advanced/auth/test_with_httpbin.rb +202 -0
  37. data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
  38. data/examples/advanced/callback_monitoring.rb +679 -0
  39. data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
  40. data/examples/advanced/mas/loop_workflow.rb +28 -0
  41. data/examples/advanced/mas/mock_planner.rb +77 -0
  42. data/examples/advanced/mas/proper_delegation_example.rb +276 -0
  43. data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
  44. data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
  45. data/examples/advanced/mcp/mcp_server_async.rb +76 -0
  46. data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
  47. data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
  48. data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
  49. data/examples/advanced/random_calculator.rb +104 -0
  50. data/examples/advanced/sleep_agent.rb +153 -0
  51. data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
  52. data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
  53. data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
  54. data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
  55. data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
  56. data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
  57. data/examples/tools/oauth2_example.rb +136 -0
  58. data/examples/tools/sleepy_tool.rb +42 -0
  59. data/lib/legate/activity_log.rb +71 -0
  60. data/lib/legate/agent.rb +959 -0
  61. data/lib/legate/agent_code_generator.rb +185 -0
  62. data/lib/legate/agent_definition.rb +812 -0
  63. data/lib/legate/agentic/decision.rb +49 -0
  64. data/lib/legate/agentic/loop.rb +134 -0
  65. data/lib/legate/agentic.rb +5 -0
  66. data/lib/legate/agents/loop_agent.rb +248 -0
  67. data/lib/legate/agents/parallel_agent.rb +163 -0
  68. data/lib/legate/agents/sequential_agent.rb +190 -0
  69. data/lib/legate/agents.rb +14 -0
  70. data/lib/legate/auth/config.rb +148 -0
  71. data/lib/legate/auth/coordinator.rb +218 -0
  72. data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
  73. data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
  74. data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
  75. data/lib/legate/auth/credential.rb +157 -0
  76. data/lib/legate/auth/encryption.rb +108 -0
  77. data/lib/legate/auth/error.rb +94 -0
  78. data/lib/legate/auth/exchanged_credential.rb +180 -0
  79. data/lib/legate/auth/excon_middleware.rb +285 -0
  80. data/lib/legate/auth/http_client_utils.rb +364 -0
  81. data/lib/legate/auth/manager.rb +531 -0
  82. data/lib/legate/auth/manager_store.rb +394 -0
  83. data/lib/legate/auth/middleware_factory.rb +290 -0
  84. data/lib/legate/auth/runner.rb +279 -0
  85. data/lib/legate/auth/scheme.rb +125 -0
  86. data/lib/legate/auth/schemes/api_key.rb +212 -0
  87. data/lib/legate/auth/schemes/google_service_account.rb +108 -0
  88. data/lib/legate/auth/schemes/http_bearer.rb +98 -0
  89. data/lib/legate/auth/schemes/oauth2.rb +396 -0
  90. data/lib/legate/auth/schemes/openid_connect.rb +346 -0
  91. data/lib/legate/auth/schemes/service_account.rb +388 -0
  92. data/lib/legate/auth/schemes.rb +40 -0
  93. data/lib/legate/auth/token_manager.rb +362 -0
  94. data/lib/legate/auth/token_store.rb +86 -0
  95. data/lib/legate/auth/tool_context_extension.rb +97 -0
  96. data/lib/legate/auth/tool_integration.rb +188 -0
  97. data/lib/legate/auth/url_guard.rb +81 -0
  98. data/lib/legate/auth.rb +453 -0
  99. data/lib/legate/callbacks/callback_context.rb +71 -0
  100. data/lib/legate/cli/agent_commands.rb +950 -0
  101. data/lib/legate/cli/auth_commands.rb +520 -0
  102. data/lib/legate/cli/base_command.rb +24 -0
  103. data/lib/legate/cli/deployment_commands.rb +934 -0
  104. data/lib/legate/cli/output_helper.rb +108 -0
  105. data/lib/legate/cli/session_commands.rb +138 -0
  106. data/lib/legate/cli/skaffold_commands.rb +223 -0
  107. data/lib/legate/cli/tool_commands.rb +261 -0
  108. data/lib/legate/cli/web_commands.rb +182 -0
  109. data/lib/legate/cli.rb +40 -0
  110. data/lib/legate/configuration/webhooks.rb +113 -0
  111. data/lib/legate/configuration.rb +39 -0
  112. data/lib/legate/definition_store.rb +23 -0
  113. data/lib/legate/errors.rb +118 -0
  114. data/lib/legate/event.rb +161 -0
  115. data/lib/legate/gemini_ai_beta_patch.rb +39 -0
  116. data/lib/legate/generators/agent_generator.rb +412 -0
  117. data/lib/legate/generators/code_validator.rb +48 -0
  118. data/lib/legate/generators/legate/install_generator.rb +35 -0
  119. data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
  120. data/lib/legate/generators/legate/templates/initializer.rb +18 -0
  121. data/lib/legate/generators/runtime_tool_loader.rb +76 -0
  122. data/lib/legate/generators/tool_generator.rb +408 -0
  123. data/lib/legate/generators.rb +11 -0
  124. data/lib/legate/global_definition_registry.rb +506 -0
  125. data/lib/legate/global_tool_manager.rb +135 -0
  126. data/lib/legate/llm/adapter.rb +69 -0
  127. data/lib/legate/llm/gemini.rb +172 -0
  128. data/lib/legate/llm/ollama.rb +80 -0
  129. data/lib/legate/llm.rb +34 -0
  130. data/lib/legate/mcp/client.rb +320 -0
  131. data/lib/legate/mcp/connection/sse.rb +292 -0
  132. data/lib/legate/mcp/connection/stdio.rb +273 -0
  133. data/lib/legate/mcp/connection_manager.rb +103 -0
  134. data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
  135. data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
  136. data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
  137. data/lib/legate/mcp/tool_wrapper.rb +138 -0
  138. data/lib/legate/mcp/util/schema_converter.rb +134 -0
  139. data/lib/legate/mcp.rb +23 -0
  140. data/lib/legate/plan_executor.rb +375 -0
  141. data/lib/legate/planner.rb +839 -0
  142. data/lib/legate/rails/railtie.rb +43 -0
  143. data/lib/legate/rails.rb +9 -0
  144. data/lib/legate/redaction.rb +32 -0
  145. data/lib/legate/session.rb +299 -0
  146. data/lib/legate/session_service/active_record.rb +300 -0
  147. data/lib/legate/session_service/base.rb +68 -0
  148. data/lib/legate/session_service/event_broadcast.rb +74 -0
  149. data/lib/legate/session_service/in_memory.rb +188 -0
  150. data/lib/legate/tool/metadata_dsl.rb +122 -0
  151. data/lib/legate/tool.rb +276 -0
  152. data/lib/legate/tool_code_generator.rb +103 -0
  153. data/lib/legate/tool_context.rb +350 -0
  154. data/lib/legate/tool_loader.rb +39 -0
  155. data/lib/legate/tool_registry.rb +73 -0
  156. data/lib/legate/tool_result.rb +61 -0
  157. data/lib/legate/tools/agent_tool.rb +187 -0
  158. data/lib/legate/tools/base/http_client.rb +319 -0
  159. data/lib/legate/tools/base/safe_url.rb +56 -0
  160. data/lib/legate/tools/base_async_job_tool.rb +91 -0
  161. data/lib/legate/tools/calculator.rb +89 -0
  162. data/lib/legate/tools/cat_facts.rb +81 -0
  163. data/lib/legate/tools/check_job_status_tool.rb +48 -0
  164. data/lib/legate/tools/current_time_tool.rb +64 -0
  165. data/lib/legate/tools/echo.rb +43 -0
  166. data/lib/legate/tools/http_request_tool.rb +105 -0
  167. data/lib/legate/tools/random_number_tool.rb +64 -0
  168. data/lib/legate/tools/read_webpage_tool.rb +92 -0
  169. data/lib/legate/tools/sleepy_tool.rb +74 -0
  170. data/lib/legate/tools/webhook_tool.rb +146 -0
  171. data/lib/legate/version.rb +5 -0
  172. data/lib/legate/web/app.rb +984 -0
  173. data/lib/legate/web/public/css/main.css +4980 -0
  174. data/lib/legate/web/public/images/favicon-256.png +0 -0
  175. data/lib/legate/web/public/images/favicon-32.png +0 -0
  176. data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
  177. data/lib/legate/web/public/images/legate-logo-light.png +0 -0
  178. data/lib/legate/web/public/js/legate.js +616 -0
  179. data/lib/legate/web/public/styles/main.scss +4402 -0
  180. data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
  181. data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
  182. data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
  183. data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
  184. data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
  185. data/lib/legate/web/routes/api_routes.rb +56 -0
  186. data/lib/legate/web/routes/authentication_routes.rb +1541 -0
  187. data/lib/legate/web/routes/core_routes.rb +111 -0
  188. data/lib/legate/web/routes/documentation_routes.rb +220 -0
  189. data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
  190. data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
  191. data/lib/legate/web/sass_compiler.rb +73 -0
  192. data/lib/legate/web/views/_active_session_info.slim +25 -0
  193. data/lib/legate/web/views/_activity_list.slim +55 -0
  194. data/lib/legate/web/views/_agent_card.slim +56 -0
  195. data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
  196. data/lib/legate/web/views/_agent_status_controls.slim +71 -0
  197. data/lib/legate/web/views/_agent_tool_table.slim +74 -0
  198. data/lib/legate/web/views/_chat_message.slim +95 -0
  199. data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
  200. data/lib/legate/web/views/_display_agent_description.slim +11 -0
  201. data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
  202. data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
  203. data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
  204. data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
  205. data/lib/legate/web/views/_display_agent_model.slim +17 -0
  206. data/lib/legate/web/views/_display_agent_name.slim +42 -0
  207. data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
  208. data/lib/legate/web/views/_display_agent_type.slim +65 -0
  209. data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
  210. data/lib/legate/web/views/_edit_agent_description.slim +16 -0
  211. data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
  212. data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
  213. data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
  214. data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
  215. data/lib/legate/web/views/_edit_agent_model.slim +23 -0
  216. data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
  217. data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
  218. data/lib/legate/web/views/_edit_agent_type.slim +67 -0
  219. data/lib/legate/web/views/_session_error.slim +4 -0
  220. data/lib/legate/web/views/_skeleton.slim +69 -0
  221. data/lib/legate/web/views/_tool_card.slim +9 -0
  222. data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
  223. data/lib/legate/web/views/agent.slim +436 -0
  224. data/lib/legate/web/views/agent_auth.slim +562 -0
  225. data/lib/legate/web/views/agents.slim +369 -0
  226. data/lib/legate/web/views/auth.slim +112 -0
  227. data/lib/legate/web/views/auth_credential_detail.slim +327 -0
  228. data/lib/legate/web/views/auth_credentials.slim +261 -0
  229. data/lib/legate/web/views/auth_debug.slim +94 -0
  230. data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
  231. data/lib/legate/web/views/auth_mapping_new.slim +123 -0
  232. data/lib/legate/web/views/auth_mappings.slim +120 -0
  233. data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
  234. data/lib/legate/web/views/auth_schemes.slim +259 -0
  235. data/lib/legate/web/views/auth_test.slim +418 -0
  236. data/lib/legate/web/views/chat.slim +192 -0
  237. data/lib/legate/web/views/docs_index.slim +105 -0
  238. data/lib/legate/web/views/docs_show.slim +105 -0
  239. data/lib/legate/web/views/error_404.slim +5 -0
  240. data/lib/legate/web/views/index.slim +148 -0
  241. data/lib/legate/web/views/layout.slim +144 -0
  242. data/lib/legate/web/views/tool_detail.slim +87 -0
  243. data/lib/legate/web/views/tools.slim +50 -0
  244. data/lib/legate/web/webhook_listener.rb +367 -0
  245. data/lib/legate/web.rb +9 -0
  246. data/lib/legate.rb +220 -0
  247. data/public/docs/advanced/callbacks.md +828 -0
  248. data/public/docs/advanced/mcp_schema_conversion.md +59 -0
  249. data/public/docs/authentication/api_reference/config.md +210 -0
  250. data/public/docs/authentication/api_reference/credential.md +246 -0
  251. data/public/docs/authentication/api_reference/encryption.md +218 -0
  252. data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
  253. data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
  254. data/public/docs/authentication/api_reference/index.md +30 -0
  255. data/public/docs/authentication/api_reference/scheme.md +250 -0
  256. data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
  257. data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
  258. data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
  259. data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
  260. data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
  261. data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
  262. data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
  263. data/public/docs/authentication/api_reference/token_manager.md +221 -0
  264. data/public/docs/authentication/api_reference/token_store.md +146 -0
  265. data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
  266. data/public/docs/authentication/guides/api_key.md +190 -0
  267. data/public/docs/authentication/guides/bearer.md +172 -0
  268. data/public/docs/authentication/guides/configuration.md +255 -0
  269. data/public/docs/authentication/guides/custom_flow.md +523 -0
  270. data/public/docs/authentication/guides/index.md +24 -0
  271. data/public/docs/authentication/guides/migration.md +435 -0
  272. data/public/docs/authentication/guides/oauth2.md +252 -0
  273. data/public/docs/authentication/guides/oidc.md +241 -0
  274. data/public/docs/authentication/guides/overview.md +155 -0
  275. data/public/docs/authentication/guides/secure_storage.md +301 -0
  276. data/public/docs/authentication/guides/service_account.md +228 -0
  277. data/public/docs/authentication/guides/token_lifecycle.md +295 -0
  278. data/public/docs/authentication/guides/web_ui_integration.md +504 -0
  279. data/public/docs/authentication/index.md +58 -0
  280. data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
  281. data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
  282. data/public/docs/authentication/troubleshooting/index.md +11 -0
  283. data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
  284. data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
  285. data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
  286. data/public/docs/cli/legate_cli_usage.md +363 -0
  287. data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
  288. data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
  289. data/public/docs/core_concepts/legate_configuration.md +116 -0
  290. data/public/docs/core_concepts/legate_definition_store.md +102 -0
  291. data/public/docs/core_concepts/legate_planner.md +94 -0
  292. data/public/docs/core_concepts/legate_session_service.md +104 -0
  293. data/public/docs/error_handling/legate_error_handling.md +122 -0
  294. data/public/docs/examples.md +199 -0
  295. data/public/docs/getting_started.md +111 -0
  296. data/public/docs/guides/agentic_agents.md +137 -0
  297. data/public/docs/guides/ai_code_generators.md +437 -0
  298. data/public/docs/guides/auto_loading.md +326 -0
  299. data/public/docs/guides/configuring_agent_webhooks.md +219 -0
  300. data/public/docs/guides/http_client_usage.md +264 -0
  301. data/public/docs/guides/llm_providers.md +137 -0
  302. data/public/docs/guides/mcp_client_integration.md +232 -0
  303. data/public/docs/guides/mcp_server_exposure.md +206 -0
  304. data/public/docs/guides/rails_integration.md +128 -0
  305. data/public/docs/guides/sending_outbound_webhooks.md +227 -0
  306. data/public/docs/guides/streaming.md +112 -0
  307. data/public/docs/guides/webhooks.md +288 -0
  308. data/public/docs/introduction.md +51 -0
  309. data/public/docs/multi_agent_systems/advanced_features.md +57 -0
  310. data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
  311. data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
  312. data/public/docs/multi_agent_systems/state_management.md +47 -0
  313. data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
  314. data/public/docs/tools/legate_built_in_tools.md +332 -0
  315. data/public/docs/tools/legate_tools_and_registry.md +263 -0
  316. data/public/docs/web_ui/legate_web_ui.md +137 -0
  317. metadata +823 -0
@@ -0,0 +1,626 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Custom Authentication Flows Example
5
+ #
6
+ # This example demonstrates advanced authentication patterns including:
7
+ # - Creating custom authentication schemes
8
+ # - Multi-step authentication flows
9
+ # - Custom authentication middleware
10
+ # - Conditional authentication based on request properties
11
+ # - Authentication delegation and chaining
12
+ # - Custom token formats and validation
13
+ #
14
+ # Usage:
15
+ # ruby examples/advanced/auth/custom_auth_flows_example.rb [--flow basic|digest|multi_step|conditional]
16
+
17
+ require 'bundler/setup'
18
+ require 'legate'
19
+ require 'legate/auth'
20
+ require 'base64'
21
+ require 'digest'
22
+ require 'optparse'
23
+ require 'securerandom'
24
+
25
+ # Parse command line options
26
+ options = {
27
+ flow: 'basic',
28
+ verbose: false
29
+ }
30
+
31
+ OptionParser.new do |opts|
32
+ opts.banner = "Usage: ruby #{__FILE__} [options]"
33
+
34
+ opts.on('--flow FLOW', %w[basic digest multi_step conditional], 'Authentication flow to demonstrate') do |flow|
35
+ options[:flow] = flow
36
+ end
37
+
38
+ opts.on('--verbose', 'Enable verbose output') do
39
+ options[:verbose] = true
40
+ end
41
+
42
+ opts.on('--help', 'Show this help') do
43
+ puts opts
44
+ exit
45
+ end
46
+ end.parse!
47
+
48
+ puts '=== Custom Authentication Flows Example ==='
49
+ puts "Flow: #{options[:flow]}"
50
+ puts
51
+
52
+ # 1. Custom Basic Authentication Scheme
53
+ # Extends the basic auth pattern with custom headers and validation
54
+ class CustomBasicAuthScheme < Legate::Auth::Scheme
55
+ def initialize(realm: 'Protected Area', custom_header: nil)
56
+ super()
57
+ @realm = realm
58
+ @custom_header = custom_header
59
+ end
60
+
61
+ def scheme_type
62
+ :custom_basic
63
+ end
64
+
65
+ def apply_to_request(credential, params = {})
66
+ username = credential[:username] || credential[:username, resolve_env: true]
67
+ password = credential[:password] || credential[:password, resolve_env: true]
68
+
69
+ raise Legate::Auth::Error, 'Username is required for custom basic auth' unless username
70
+ raise Legate::Auth::Error, 'Password is required for custom basic auth' unless password
71
+
72
+ # Create basic auth header
73
+ encoded = Base64.strict_encode64("#{username}:#{password}")
74
+
75
+ headers = params[:headers] || {}
76
+ headers['Authorization'] = "Basic #{encoded}"
77
+
78
+ # Add custom header if specified
79
+ headers[@custom_header] = "Basic-#{@realm}" if @custom_header
80
+
81
+ # Add client identification
82
+ headers['User-Agent'] = 'Legate-CustomAuth/1.0'
83
+
84
+ { headers: headers }
85
+ end
86
+
87
+ def to_h
88
+ {
89
+ scheme_type: scheme_type,
90
+ realm: @realm,
91
+ custom_header: @custom_header
92
+ }
93
+ end
94
+ end
95
+
96
+ # 2. Custom Digest Authentication Scheme
97
+ # Implements digest authentication with custom challenge handling
98
+ class CustomDigestAuthScheme < Legate::Auth::Scheme
99
+ def initialize(realm: 'Protected Area')
100
+ super()
101
+ @realm = realm
102
+ @nonce_count = 0
103
+ end
104
+
105
+ def scheme_type
106
+ :custom_digest
107
+ end
108
+
109
+ def apply_to_request(credential, params = {})
110
+ username = credential[:username] || credential[:username, resolve_env: true]
111
+ password = credential[:password] || credential[:password, resolve_env: true]
112
+
113
+ raise Legate::Auth::Error, 'Username is required for digest auth' unless username
114
+ raise Legate::Auth::Error, 'Password is required for digest auth' unless password
115
+
116
+ # Simulate digest challenge (in real implementation this would come from a 401 response)
117
+ nonce = params[:nonce] || SecureRandom.hex(16)
118
+ uri = params[:uri] || '/'
119
+ method = params[:method] || 'GET'
120
+
121
+ @nonce_count += 1
122
+ nc = format('%08x', @nonce_count)
123
+ cnonce = SecureRandom.hex(8)
124
+
125
+ # Calculate digest response
126
+ ha1 = Digest::MD5.hexdigest("#{username}:#{@realm}:#{password}")
127
+ ha2 = Digest::MD5.hexdigest("#{method}:#{uri}")
128
+ response = Digest::MD5.hexdigest("#{ha1}:#{nonce}:#{nc}:#{cnonce}:auth:#{ha2}")
129
+
130
+ # Build digest header
131
+ digest_header = [
132
+ "Digest username=\"#{username}\"",
133
+ "realm=\"#{@realm}\"",
134
+ "nonce=\"#{nonce}\"",
135
+ "uri=\"#{uri}\"",
136
+ "response=\"#{response}\"",
137
+ 'algorithm=MD5',
138
+ 'qop=auth',
139
+ "nc=#{nc}",
140
+ "cnonce=\"#{cnonce}\""
141
+ ].join(', ')
142
+
143
+ headers = params[:headers] || {}
144
+ headers['Authorization'] = digest_header
145
+
146
+ { headers: headers }
147
+ end
148
+
149
+ def to_h
150
+ {
151
+ scheme_type: scheme_type,
152
+ realm: @realm,
153
+ nonce_count: @nonce_count
154
+ }
155
+ end
156
+ end
157
+
158
+ # 3. Multi-Step Authentication Scheme
159
+ # Demonstrates a multi-step authentication flow with state management
160
+ class MultiStepAuthScheme < Legate::Auth::Scheme
161
+ def initialize
162
+ super()
163
+ @auth_state = {}
164
+ end
165
+
166
+ def scheme_type
167
+ :multi_step
168
+ end
169
+
170
+ def apply_to_request(credential, params = {})
171
+ step = params[:step] || 1
172
+ session_id = params[:session_id] || SecureRandom.hex(16)
173
+
174
+ case step
175
+ when 1
176
+ # Step 1: Initial authentication
177
+ perform_step_1(credential, session_id)
178
+ when 2
179
+ # Step 2: Second factor
180
+ perform_step_2(credential, session_id, params)
181
+ when 3
182
+ # Step 3: Final token
183
+ perform_step_3(credential, session_id, params)
184
+ else
185
+ raise Legate::Auth::Error, "Invalid authentication step: #{step}"
186
+ end
187
+ end
188
+
189
+ def to_h
190
+ {
191
+ scheme_type: scheme_type,
192
+ active_sessions: @auth_state.keys.length
193
+ }
194
+ end
195
+
196
+ private
197
+
198
+ def perform_step_1(credential, session_id)
199
+ username = credential[:username] || credential[:username, resolve_env: true]
200
+ password = credential[:password] || credential[:password, resolve_env: true]
201
+
202
+ raise Legate::Auth::Error, 'Username required for step 1' unless username
203
+ raise Legate::Auth::Error, 'Password required for step 1' unless password
204
+
205
+ # Simulate password validation
206
+ raise Legate::Auth::Error, 'Invalid credentials' unless username && password
207
+
208
+ # Store session state
209
+ @auth_state[session_id] = {
210
+ username: username,
211
+ step: 1,
212
+ timestamp: Time.now,
213
+ verified_factors: []
214
+ }
215
+
216
+ step_1_token = Base64.strict_encode64("step1:#{session_id}:#{username}")
217
+
218
+ {
219
+ headers: {
220
+ 'X-Auth-Step' => '1',
221
+ 'X-Auth-Token' => step_1_token,
222
+ 'X-Session-ID' => session_id
223
+ },
224
+ next_step: 2,
225
+ session_id: session_id
226
+ }
227
+ end
228
+
229
+ def perform_step_2(credential, session_id, params)
230
+ session = @auth_state[session_id]
231
+ raise Legate::Auth::Error, 'Invalid session' unless session
232
+ raise Legate::Auth::Error, 'Must complete step 1 first' unless session[:step] >= 1
233
+
234
+ # Second factor (could be TOTP, SMS, etc.)
235
+ second_factor = credential[:second_factor] || params[:second_factor] || '123456'
236
+
237
+ # Simulate second factor validation
238
+ raise Legate::Auth::Error, 'Invalid second factor' unless second_factor == '123456' # Demo validation
239
+
240
+ session[:step] = 2
241
+ session[:verified_factors] << 'password'
242
+ session[:verified_factors] << 'second_factor'
243
+
244
+ step_2_token = Base64.strict_encode64("step2:#{session_id}:#{session[:username]}")
245
+
246
+ {
247
+ headers: {
248
+ 'X-Auth-Step' => '2',
249
+ 'X-Auth-Token' => step_2_token,
250
+ 'X-Session-ID' => session_id
251
+ },
252
+ next_step: 3,
253
+ session_id: session_id
254
+ }
255
+ end
256
+
257
+ def perform_step_3(_credential, session_id, _params)
258
+ session = @auth_state[session_id]
259
+ raise Legate::Auth::Error, 'Invalid session' unless session
260
+ raise Legate::Auth::Error, 'Must complete step 2 first' unless session[:step] >= 2
261
+
262
+ # Generate final access token
263
+ session[:step] = 3
264
+ session[:completed_at] = Time.now
265
+
266
+ final_token = Base64.strict_encode64("final:#{session_id}:#{session[:username]}:#{Time.now.to_i}")
267
+
268
+ {
269
+ headers: {
270
+ 'Authorization' => "Bearer #{final_token}",
271
+ 'X-Auth-Complete' => 'true'
272
+ },
273
+ access_token: final_token,
274
+ session_id: session_id,
275
+ authenticated: true
276
+ }
277
+ end
278
+ end
279
+
280
+ # 4. Conditional Authentication Scheme
281
+ # Applies different authentication methods based on request context
282
+ class ConditionalAuthScheme < Legate::Auth::Scheme
283
+ def initialize
284
+ super()
285
+ @api_key_scheme = Legate::Auth::Schemes::ApiKey.new
286
+ @bearer_scheme = Legate::Auth::Schemes::HTTPBearer.new
287
+ end
288
+
289
+ def scheme_type
290
+ :conditional
291
+ end
292
+
293
+ def apply_to_request(credential, params = {})
294
+ # Determine authentication method based on request context
295
+ auth_method = determine_auth_method(params)
296
+
297
+ case auth_method
298
+ when :api_key
299
+ puts 'Using API Key authentication for this request'
300
+ @api_key_scheme.apply_to_request(credential, params)
301
+ when :bearer
302
+ puts 'Using Bearer token authentication for this request'
303
+ @bearer_scheme.apply_to_request(credential, params)
304
+ when :none
305
+ puts 'No authentication required for this request'
306
+ { headers: {} }
307
+ else
308
+ raise Legate::Auth::Error, 'No suitable authentication method available'
309
+ end
310
+ end
311
+
312
+ def to_h
313
+ {
314
+ scheme_type: scheme_type,
315
+ available_methods: %i[api_key bearer none]
316
+ }
317
+ end
318
+
319
+ private
320
+
321
+ def determine_auth_method(params)
322
+ url = params[:url] || ''
323
+ method = params[:method] || 'GET'
324
+ headers = params[:headers] || {}
325
+
326
+ # Public endpoints don't need auth
327
+ return :none if url.include?('/public/') || url.include?('/health')
328
+
329
+ # API endpoints prefer API key
330
+ return :api_key if url.include?('/api/') && method != 'GET'
331
+
332
+ # Authenticated endpoints use bearer tokens
333
+ return :bearer if headers['Accept']&.include?('application/json')
334
+
335
+ # Default to API key
336
+ :api_key
337
+ end
338
+ end
339
+
340
+ # 5. Custom Authentication Middleware
341
+ # Demonstrates custom middleware that can handle multiple schemes
342
+ class CustomAuthMiddleware
343
+ def initialize(app, schemes: {}, default_scheme: nil)
344
+ @app = app
345
+ @schemes = schemes
346
+ @default_scheme = default_scheme
347
+ end
348
+
349
+ def call(env)
350
+ # Extract request information
351
+ request_path = env['PATH_INFO'] || '/'
352
+ request_method = env['REQUEST_METHOD'] || 'GET'
353
+
354
+ # Determine which scheme to use
355
+ scheme_name = determine_scheme(request_path, request_method)
356
+ scheme = @schemes[scheme_name] || @schemes[@default_scheme]
357
+
358
+ if scheme
359
+ # Apply authentication
360
+ begin
361
+ auth_result = scheme.apply_to_request(
362
+ get_credential_for_scheme(scheme_name),
363
+ {
364
+ url: request_path,
365
+ method: request_method,
366
+ headers: extract_headers(env)
367
+ }
368
+ )
369
+
370
+ # Add auth headers to the request
371
+ if auth_result[:headers]
372
+ auth_result[:headers].each do |key, value|
373
+ env["HTTP_#{key.upcase.tr('-', '_')}"] = value
374
+ end
375
+ end
376
+
377
+ puts "Applied #{scheme_name} authentication to #{request_method} #{request_path}"
378
+ rescue Legate::Auth::Error => e
379
+ puts "Authentication failed: #{e.message}"
380
+ return [401, { 'Content-Type' => 'text/plain' }, ['Authentication required']]
381
+ end
382
+ end
383
+
384
+ @app.call(env)
385
+ end
386
+
387
+ private
388
+
389
+ def determine_scheme(path, _method)
390
+ return :multi_step if path.include?('/secure/')
391
+ return :conditional if path.include?('/api/')
392
+ return :custom_digest if path.include?('/digest/')
393
+
394
+ @default_scheme
395
+ end
396
+
397
+ def get_credential_for_scheme(scheme_name)
398
+ # In a real application, you'd retrieve appropriate credentials
399
+ case scheme_name
400
+ when :custom_basic, :custom_digest, :multi_step
401
+ Legate::Auth::Credential.new(
402
+ auth_type: :basic,
403
+ username: 'demo_user',
404
+ password: 'demo_pass'
405
+ )
406
+ when :conditional
407
+ Legate::Auth::Credential.new(
408
+ auth_type: :api_key,
409
+ api_key: 'demo_api_key_123'
410
+ )
411
+ else
412
+ Legate::Auth::Credential.new(
413
+ auth_type: :api_key,
414
+ api_key: 'default_key_456'
415
+ )
416
+ end
417
+ end
418
+
419
+ def extract_headers(env)
420
+ headers = {}
421
+ env.each do |key, value|
422
+ if key.start_with?('HTTP_')
423
+ header_name = key[5..-1].split('_').map(&:capitalize).join('-')
424
+ headers[header_name] = value
425
+ end
426
+ end
427
+ headers
428
+ end
429
+ end
430
+
431
+ # Demonstration based on selected flow
432
+ case options[:flow]
433
+ when 'basic'
434
+ puts '=== Custom Basic Authentication Demo ==='
435
+
436
+ scheme = CustomBasicAuthScheme.new(
437
+ realm: 'Demo Protected Area',
438
+ custom_header: 'X-Custom-Auth'
439
+ )
440
+
441
+ credential = Legate::Auth::Credential.new(
442
+ auth_type: :basic,
443
+ username: 'demo_user',
444
+ password: 'secret_password'
445
+ )
446
+
447
+ puts "Created custom basic auth scheme with realm: 'Demo Protected Area'"
448
+ puts "Scheme type: #{scheme.scheme_type}"
449
+ puts
450
+
451
+ # Apply authentication
452
+ result = scheme.apply_to_request(credential)
453
+ puts 'Authentication applied successfully!'
454
+ puts 'Headers added:'
455
+ result[:headers].each { |k, v| puts " #{k}: #{v}" }
456
+
457
+ when 'digest'
458
+ puts '=== Custom Digest Authentication Demo ==='
459
+
460
+ scheme = CustomDigestAuthScheme.new(realm: 'Digest Protected')
461
+
462
+ credential = Legate::Auth::Credential.new(
463
+ auth_type: :basic,
464
+ username: 'digest_user',
465
+ password: 'digest_pass'
466
+ )
467
+
468
+ puts 'Created custom digest auth scheme'
469
+ puts "Scheme type: #{scheme.scheme_type}"
470
+ puts
471
+
472
+ # Simulate multiple requests with digest auth
473
+ 3.times do |i|
474
+ puts "Request #{i + 1}:"
475
+ result = scheme.apply_to_request(
476
+ credential,
477
+ {
478
+ uri: "/protected/resource#{i + 1}",
479
+ method: 'GET',
480
+ nonce: SecureRandom.hex(16)
481
+ }
482
+ )
483
+
484
+ auth_header = result[:headers]['Authorization']
485
+ puts " Authorization: #{auth_header[0..80]}..." if auth_header.length > 80
486
+ puts
487
+ end
488
+
489
+ when 'multi_step'
490
+ puts '=== Multi-Step Authentication Demo ==='
491
+
492
+ scheme = MultiStepAuthScheme.new
493
+
494
+ credential = Legate::Auth::Credential.new(
495
+ auth_type: :basic,
496
+ username: 'multi_user',
497
+ password: 'multi_pass',
498
+ second_factor: '123456'
499
+ )
500
+
501
+ puts 'Created multi-step auth scheme'
502
+ puts "Scheme type: #{scheme.scheme_type}"
503
+ puts
504
+
505
+ session_id = nil
506
+
507
+ # Step 1
508
+ puts 'Step 1: Initial authentication'
509
+ result1 = scheme.apply_to_request(credential, { step: 1 })
510
+ session_id = result1[:session_id]
511
+ puts " Next step: #{result1[:next_step]}"
512
+ puts " Session ID: #{session_id}"
513
+ puts " Headers: #{result1[:headers]}"
514
+ puts
515
+
516
+ # Step 2
517
+ puts 'Step 2: Second factor authentication'
518
+ result2 = scheme.apply_to_request(credential, { step: 2, session_id: session_id })
519
+ puts " Next step: #{result2[:next_step]}"
520
+ puts " Headers: #{result2[:headers]}"
521
+ puts
522
+
523
+ # Step 3
524
+ puts 'Step 3: Final token generation'
525
+ result3 = scheme.apply_to_request(credential, { step: 3, session_id: session_id })
526
+ puts " Authenticated: #{result3[:authenticated]}"
527
+ puts " Access token: #{result3[:access_token][0..20]}..."
528
+ puts " Headers: #{result3[:headers]}"
529
+
530
+ when 'conditional'
531
+ puts '=== Conditional Authentication Demo ==='
532
+
533
+ scheme = ConditionalAuthScheme.new
534
+
535
+ # Create a credential that can work with multiple auth types
536
+ credential = Legate::Auth::Credential.new(
537
+ auth_type: :api_key,
538
+ api_key: 'demo_api_key_789',
539
+ bearer_token: 'demo_bearer_token_456'
540
+ )
541
+
542
+ puts 'Created conditional auth scheme'
543
+ puts "Scheme type: #{scheme.scheme_type}"
544
+ puts
545
+
546
+ # Test different request contexts
547
+ test_requests = [
548
+ { url: '/public/info', method: 'GET', description: 'Public endpoint' },
549
+ { url: '/api/users', method: 'POST', description: 'API endpoint (non-GET)' },
550
+ { url: '/dashboard', method: 'GET', headers: { 'Accept' => 'application/json' }, description: 'JSON API request' },
551
+ { url: '/health', method: 'GET', description: 'Health check endpoint' },
552
+ { url: '/api/data', method: 'GET', description: 'API endpoint (GET)' }
553
+ ]
554
+
555
+ test_requests.each do |req|
556
+ puts "Testing: #{req[:description]}"
557
+ puts " #{req[:method]} #{req[:url]}"
558
+
559
+ begin
560
+ result = scheme.apply_to_request(credential, req)
561
+ puts " Result: #{result[:headers].any? ? result[:headers] : 'No authentication required'}"
562
+ rescue StandardError => e
563
+ puts " Error: #{e.message}"
564
+ end
565
+ puts
566
+ end
567
+
568
+ else
569
+ puts "Unknown flow: #{options[:flow]}"
570
+ exit 1
571
+ end
572
+
573
+ # Custom middleware demonstration
574
+ puts "\n=== Custom Authentication Middleware Demo ==="
575
+
576
+ # Create a simple WSGI-style app for demonstration
577
+ demo_app = lambda do |env|
578
+ path = env['PATH_INFO']
579
+ method = env['REQUEST_METHOD']
580
+ [200, { 'Content-Type' => 'text/plain' }, ["Success: #{method} #{path}"]]
581
+ end
582
+
583
+ # Create custom middleware with multiple schemes
584
+ middleware = CustomAuthMiddleware.new(
585
+ demo_app,
586
+ schemes: {
587
+ custom_basic: CustomBasicAuthScheme.new,
588
+ custom_digest: CustomDigestAuthScheme.new,
589
+ multi_step: MultiStepAuthScheme.new,
590
+ conditional: ConditionalAuthScheme.new
591
+ },
592
+ default_scheme: :custom_basic
593
+ )
594
+
595
+ # Simulate requests through the middleware
596
+ test_paths = [
597
+ ['GET', '/public/info'],
598
+ ['GET', '/api/data'],
599
+ ['POST', '/secure/upload'],
600
+ ['GET', '/digest/protected']
601
+ ]
602
+
603
+ puts 'Simulating requests through custom middleware:'
604
+ test_paths.each do |method, path|
605
+ puts "\n#{method} #{path}:"
606
+ env = {
607
+ 'REQUEST_METHOD' => method,
608
+ 'PATH_INFO' => path,
609
+ 'HTTP_ACCEPT' => 'application/json'
610
+ }
611
+
612
+ status, headers, body = middleware.call(env)
613
+ puts " Status: #{status}"
614
+ puts " Response: #{body.first}" if body.first
615
+ end
616
+
617
+ puts "\n=== Custom Authentication Flows Demo Complete ==="
618
+ puts "\nKey concepts demonstrated:"
619
+ puts '• Custom authentication scheme implementation'
620
+ puts '• Multi-step authentication workflows'
621
+ puts '• Conditional authentication based on request context'
622
+ puts '• Custom authentication middleware'
623
+ puts '• State management in authentication flows'
624
+ puts '• Integration of multiple authentication methods'
625
+ puts "\nThese patterns can be used to implement complex authentication"
626
+ puts 'requirements that go beyond standard OAuth2/API key patterns.'