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,523 @@
1
+ # Custom Authentication Flow
2
+
3
+ ## Overview
4
+
5
+ While the Legate Ruby library provides built-in support for common authentication schemes, you may need to implement custom authentication flows for APIs with unique requirements. This guide explains how to create custom authentication flows using the Legate's authentication framework.
6
+
7
+ ## When to Use Custom Authentication Flows
8
+
9
+ Consider implementing a custom authentication flow in the following scenarios:
10
+
11
+ - The API uses a non-standard authentication mechanism not covered by the built-in schemes
12
+ - You need to modify the standard authentication flow with additional steps or parameters
13
+ - You need to implement a proprietary authentication protocol specific to your API
14
+ - You want to combine multiple authentication mechanisms into a single flow
15
+
16
+ ## Building Custom Authentication Schemes
17
+
18
+ ### Creating a Custom Scheme Class
19
+
20
+ Custom authentication schemes should inherit from the `Legate::Auth::Scheme` base class:
21
+
22
+ ```ruby
23
+ module Legate
24
+ module Auth
25
+ module Schemes
26
+ class CustomScheme < Legate::Auth::Scheme
27
+ attr_reader :custom_param, :auth_url, :token_url
28
+
29
+ def initialize(custom_param:, auth_url:, token_url:)
30
+ @custom_param = custom_param
31
+ @auth_url = auth_url
32
+ @token_url = token_url
33
+ end
34
+
35
+ # Define the authentication type
36
+ def auth_type
37
+ :custom
38
+ end
39
+
40
+ # Define how to apply the credentials to a request
41
+ def apply_to_request(request, credential, tokens = nil)
42
+ if tokens
43
+ # Apply obtained tokens to the request
44
+ request[:headers] ||= {}
45
+ request[:headers]['Authorization'] = "Custom #{tokens[:access_token]}"
46
+ elsif credential
47
+ # Apply initial credentials to the request
48
+ request[:headers] ||= {}
49
+ request[:headers]['X-Custom-Auth'] = credential.custom_key
50
+ end
51
+ request
52
+ end
53
+
54
+ # Define how to check if tokens are valid
55
+ def tokens_valid?(tokens)
56
+ return false unless tokens
57
+ return false unless tokens[:access_token]
58
+ return false if tokens[:expires_at] && Time.now.to_i >= tokens[:expires_at]
59
+ true
60
+ end
61
+
62
+ # Define token refresh logic
63
+ def refresh_tokens(credential, tokens)
64
+ # Implement token refresh logic
65
+ # Return new tokens or raise an error
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ ```
72
+
73
+ ### Key Methods to Implement
74
+
75
+ When creating a custom authentication scheme, implement these key methods:
76
+
77
+ | Method | Description | Return Value |
78
+ |--------|-------------|--------------|
79
+ | `auth_type` | Identifies the authentication type | Symbol (e.g., `:custom`) |
80
+ | `apply_to_request` | Applies credentials or tokens to requests | Modified request hash |
81
+ | `tokens_valid?` | Checks if tokens are valid | Boolean |
82
+ | `refresh_tokens` | Refreshes expired tokens | Hash of new tokens or raises error |
83
+
84
+ ## Implementing Interactive Authentication
85
+
86
+ For interactive authentication flows (similar to OAuth2), you'll need to implement both the client-side and server-side components.
87
+
88
+ ### Step 1: Creating an Interactive Scheme
89
+
90
+ ```ruby
91
+ class CustomInteractiveScheme < Legate::Auth::Scheme
92
+ attr_reader :auth_url, :token_url
93
+
94
+ def initialize(auth_url:, token_url:)
95
+ @auth_url = auth_url
96
+ @token_url = token_url
97
+ end
98
+
99
+ def auth_type
100
+ :custom_interactive
101
+ end
102
+
103
+ # Generate the authorization URL for the interactive flow
104
+ def generate_auth_url(credential, redirect_uri, state)
105
+ uri = URI.parse(@auth_url)
106
+ params = {
107
+ 'client_id' => credential.client_id,
108
+ 'redirect_uri' => redirect_uri,
109
+ 'state' => state,
110
+ 'response_type' => 'code',
111
+ 'custom_param' => 'custom_value'
112
+ }
113
+ uri.query = URI.encode_www_form(params)
114
+ uri.to_s
115
+ end
116
+
117
+ # Exchange the auth code for tokens
118
+ def exchange_auth_code(credential, code, redirect_uri)
119
+ response = Excon.post(
120
+ @token_url,
121
+ headers: { 'Content-Type' => 'application/x-www-form-urlencoded' },
122
+ body: URI.encode_www_form(
123
+ 'grant_type' => 'authorization_code',
124
+ 'code' => code,
125
+ 'redirect_uri' => redirect_uri,
126
+ 'client_id' => credential.client_id,
127
+ 'client_secret' => credential.client_secret
128
+ )
129
+ )
130
+
131
+ if response.status != 200
132
+ raise Legate::Auth::TokenExchangeError, "Failed to exchange code: #{response.body}"
133
+ end
134
+
135
+ parse_token_response(response.body)
136
+ end
137
+
138
+ # Parse the token response into a standard format
139
+ def parse_token_response(response_body)
140
+ data = JSON.parse(response_body)
141
+ {
142
+ access_token: data['access_token'],
143
+ refresh_token: data['refresh_token'],
144
+ token_type: data['token_type'] || 'Bearer',
145
+ expires_at: data['expires_in'] ? Time.now.to_i + data['expires_in'].to_i : nil,
146
+ scope: data['scope']
147
+ }
148
+ end
149
+
150
+ # Apply tokens to requests
151
+ def apply_to_request(request, credential, tokens = nil)
152
+ if tokens
153
+ request[:headers] ||= {}
154
+ request[:headers]['Authorization'] = "#{tokens[:token_type]} #{tokens[:access_token]}"
155
+ end
156
+ request
157
+ end
158
+
159
+ # Check if tokens are valid
160
+ def tokens_valid?(tokens)
161
+ return false unless tokens
162
+ return false unless tokens[:access_token]
163
+ return false if tokens[:expires_at] && Time.now.to_i >= tokens[:expires_at]
164
+ true
165
+ end
166
+
167
+ # Refresh tokens
168
+ def refresh_tokens(credential, tokens)
169
+ return nil unless tokens[:refresh_token]
170
+
171
+ response = Excon.post(
172
+ @token_url,
173
+ headers: { 'Content-Type' => 'application/x-www-form-urlencoded' },
174
+ body: URI.encode_www_form(
175
+ 'grant_type' => 'refresh_token',
176
+ 'refresh_token' => tokens[:refresh_token],
177
+ 'client_id' => credential.client_id,
178
+ 'client_secret' => credential.client_secret
179
+ )
180
+ )
181
+
182
+ if response.status != 200
183
+ raise Legate::Auth::TokenRefreshError, "Failed to refresh token: #{response.body}"
184
+ end
185
+
186
+ # Merge the new tokens with the existing tokens
187
+ refresh_data = parse_token_response(response.body)
188
+ tokens.merge(refresh_data)
189
+ end
190
+ end
191
+ ```
192
+
193
+ ### Step 2: Creating a Custom Authentication Coordinator
194
+
195
+ For interactive flows, create a coordinator to handle the authentication flow:
196
+
197
+ ```ruby
198
+ module Legate
199
+ module Auth
200
+ module Coordinators
201
+ class CustomInteractiveCoordinator
202
+ def initialize(scheme, credential)
203
+ @scheme = scheme
204
+ @credential = credential
205
+ end
206
+
207
+ # Handle the auth request phase
208
+ def handle_auth_request(config)
209
+ # Generate state for CSRF protection
210
+ state = SecureRandom.hex(16)
211
+
212
+ # Generate the auth URL
213
+ auth_url = @scheme.generate_auth_url(
214
+ @credential,
215
+ config.redirect_uri,
216
+ state
217
+ )
218
+
219
+ # Return the auth URL and state
220
+ {
221
+ auth_url: auth_url,
222
+ state: state
223
+ }
224
+ end
225
+
226
+ # Handle the auth response phase
227
+ def handle_auth_response(config)
228
+ # Parse the auth response URI
229
+ uri = URI.parse(config.auth_response_uri)
230
+ params = CGI.parse(uri.query || '')
231
+
232
+ # Extract the authorization code
233
+ code = params['code']&.first
234
+ unless code
235
+ raise Legate::Auth::TokenExchangeError, "Missing authorization code in response"
236
+ end
237
+
238
+ # Exchange the code for tokens
239
+ tokens = @scheme.exchange_auth_code(
240
+ @credential,
241
+ code,
242
+ config.redirect_uri
243
+ )
244
+
245
+ # Return the tokens
246
+ tokens
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
252
+ ```
253
+
254
+ ### Step 3: Using the Custom Scheme in Tools
255
+
256
+ ```ruby
257
+ # Create custom scheme and credential
258
+ custom_scheme = CustomInteractiveScheme.new(
259
+ auth_url: 'https://auth.example.com/authorize',
260
+ token_url: 'https://auth.example.com/token'
261
+ )
262
+
263
+ # Note: a custom credential still requires a valid auth_type. If your scheme
264
+ # doesn't map to a built-in type, :basic (with username/password) or one of the
265
+ # existing types can serve as the carrier for your client credentials.
266
+ custom_credential = Legate::Auth::Credential.new(
267
+ auth_type: :oauth2,
268
+ client_id: ENV['CUSTOM_CLIENT_ID'],
269
+ client_secret: ENV['CUSTOM_CLIENT_SECRET']
270
+ )
271
+
272
+ # Create a tool that uses the custom scheme and coordinator
273
+ class CustomApiTool < Legate::Tool
274
+ tool_description 'A tool that uses custom authentication'
275
+
276
+ def perform_execution(params, context)
277
+ scheme = CustomInteractiveScheme.new(
278
+ auth_url: 'https://auth.example.com/authorize',
279
+ token_url: 'https://auth.example.com/token'
280
+ )
281
+ credential = Legate::Auth::Credential.new(
282
+ auth_type: :oauth2,
283
+ client_id: ENV['CUSTOM_CLIENT_ID'],
284
+ client_secret: ENV['CUSTOM_CLIENT_SECRET']
285
+ )
286
+
287
+ # Drive interactive auth via context.with_authentication, which yields the
288
+ # auth request to your application for handling (see ToolContextExtension).
289
+ context.with_authentication do
290
+ # Once tokens are available, build a request and apply the scheme's tokens.
291
+ # Here we illustrate applying obtained tokens directly:
292
+ tokens = { access_token: 'obtained-access-token', token_type: 'Bearer' }
293
+ request = scheme.apply_to_request({ headers: {} }, credential, tokens)
294
+
295
+ conn = Excon.new('https://api.example.com')
296
+ response = conn.get(path: '/data', headers: request[:headers])
297
+ { status: :success, result: JSON.parse(response.body) }
298
+ end
299
+ end
300
+ end
301
+ ```
302
+
303
+ ## Testing Custom Authentication Flows
304
+
305
+ ### Unit Testing Schemes
306
+
307
+ ```ruby
308
+ RSpec.describe CustomInteractiveScheme do
309
+ let(:scheme) do
310
+ CustomInteractiveScheme.new(
311
+ auth_url: 'https://auth.example.com/authorize',
312
+ token_url: 'https://auth.example.com/token'
313
+ )
314
+ end
315
+
316
+ let(:credential) do
317
+ Legate::Auth::Credential.new(
318
+ auth_type: :oauth2,
319
+ client_id: 'test_client_id',
320
+ client_secret: 'test_client_secret'
321
+ )
322
+ end
323
+
324
+ describe '#generate_auth_url' do
325
+ it 'generates a valid authorization URL' do
326
+ url = scheme.generate_auth_url(credential, 'https://callback.example.com', 'state123')
327
+ uri = URI.parse(url)
328
+ params = CGI.parse(uri.query)
329
+
330
+ expect(uri.host).to eq('auth.example.com')
331
+ expect(uri.path).to eq('/authorize')
332
+ expect(params['client_id']).to eq(['test_client_id'])
333
+ expect(params['redirect_uri']).to eq(['https://callback.example.com'])
334
+ expect(params['state']).to eq(['state123'])
335
+ expect(params['response_type']).to eq(['code'])
336
+ end
337
+ end
338
+
339
+ describe '#apply_to_request' do
340
+ it 'applies tokens to the request' do
341
+ tokens = {
342
+ access_token: 'test_access_token',
343
+ token_type: 'Bearer'
344
+ }
345
+
346
+ request = {}
347
+ modified_request = scheme.apply_to_request(request, credential, tokens)
348
+
349
+ expect(modified_request[:headers]['Authorization']).to eq('Bearer test_access_token')
350
+ end
351
+ end
352
+
353
+ # Add tests for other methods
354
+ end
355
+ ```
356
+
357
+ ### Integration Testing
358
+
359
+ > The runner harness below is illustrative pseudo-code for an end-to-end test.
360
+ > Adapt it to however your application drives tools and supplies the auth
361
+ > response (e.g. through `context.with_authentication`).
362
+
363
+ ```ruby
364
+ RSpec.describe 'Custom Authentication Integration' do
365
+ let(:scheme) do
366
+ CustomInteractiveScheme.new(
367
+ auth_url: 'https://auth.example.com/authorize',
368
+ token_url: 'https://auth.example.com/token'
369
+ )
370
+ end
371
+
372
+ let(:credential) do
373
+ Legate::Auth::Credential.new(
374
+ auth_type: :oauth2,
375
+ client_id: 'test_client_id',
376
+ client_secret: 'test_client_secret'
377
+ )
378
+ end
379
+
380
+ let(:runner) { Legate::Runner.new }
381
+ let(:tool) { CustomApiTool.new }
382
+
383
+ before do
384
+ # Mock the token endpoint
385
+ stub_request(:post, 'https://auth.example.com/token')
386
+ .to_return(
387
+ status: 200,
388
+ headers: { 'Content-Type' => 'application/json' },
389
+ body: {
390
+ access_token: 'test_access_token',
391
+ refresh_token: 'test_refresh_token',
392
+ token_type: 'Bearer',
393
+ expires_in: 3600
394
+ }.to_json
395
+ )
396
+
397
+ # Mock the API endpoint
398
+ stub_request(:get, 'https://api.example.com/data')
399
+ .with(headers: { 'Authorization' => 'Bearer test_access_token' })
400
+ .to_return(
401
+ status: 200,
402
+ headers: { 'Content-Type' => 'application/json' },
403
+ body: { success: true, data: [1, 2, 3] }.to_json
404
+ )
405
+ end
406
+
407
+ it 'successfully completes the authentication flow' do
408
+ # Mock the auth response by setting up the Fiber directly
409
+ # In a real application, this would come from the user interaction
410
+ runner.auth_response = Legate::Auth::Config.new(
411
+ auth_response_uri: 'https://callback.example.com?code=test_auth_code&state=test_state',
412
+ redirect_uri: 'https://callback.example.com'
413
+ )
414
+
415
+ # Run the tool
416
+ result = runner.run(tool)
417
+
418
+ # Check the result
419
+ expect(result[:status]).to eq('success')
420
+ expect(result[:result]).to eq({ 'success' => true, 'data' => [1, 2, 3] })
421
+ end
422
+ end
423
+ ```
424
+
425
+ ## Handling Edge Cases
426
+
427
+ ### Handling Authentication Errors
428
+
429
+ Ensure your custom authentication flow properly handles various error scenarios:
430
+
431
+ ```ruby
432
+ def exchange_auth_code(credential, code, redirect_uri)
433
+ response = Excon.post(
434
+ @token_url,
435
+ headers: { 'Content-Type' => 'application/x-www-form-urlencoded' },
436
+ body: URI.encode_www_form(
437
+ 'grant_type' => 'authorization_code',
438
+ 'code' => code,
439
+ 'redirect_uri' => redirect_uri,
440
+ 'client_id' => credential.client_id,
441
+ 'client_secret' => credential.client_secret
442
+ )
443
+ )
444
+
445
+ case response.status
446
+ when 200
447
+ parse_token_response(response.body)
448
+ when 400
449
+ error_data = JSON.parse(response.body) rescue { 'error' => 'invalid_request' }
450
+ handle_error(error_data)
451
+ when 401
452
+ raise Legate::Auth::CredentialError, "Invalid client credentials"
453
+ else
454
+ raise Legate::Auth::TokenExchangeError, "Failed to exchange code: #{response.body}"
455
+ end
456
+ end
457
+
458
+ def handle_error(error_data)
459
+ error = error_data['error']
460
+
461
+ case error
462
+ when 'invalid_grant'
463
+ raise Legate::Auth::TokenExchangeError, "Invalid authorization code"
464
+ when 'invalid_client'
465
+ raise Legate::Auth::CredentialError, "Invalid client credentials"
466
+ when 'invalid_request'
467
+ raise Legate::Auth::TokenExchangeError, "Invalid request: #{error_data['error_description']}"
468
+ else
469
+ raise Legate::Auth::TokenExchangeError, "Authentication error: #{error_data['error_description'] || error}"
470
+ end
471
+ end
472
+ ```
473
+
474
+ ### Implementing Non-Standard Token Refresh
475
+
476
+ If your API has a non-standard token refresh mechanism:
477
+
478
+ ```ruby
479
+ def refresh_tokens(credential, tokens)
480
+ # Custom refresh logic
481
+ response = Excon.post(
482
+ @token_url,
483
+ headers: {
484
+ 'Content-Type' => 'application/x-www-form-urlencoded',
485
+ 'Authorization' => "Basic #{Base64.strict_encode64("#{credential.client_id}:#{credential.client_secret}")}"
486
+ },
487
+ body: URI.encode_www_form(
488
+ 'grant_type' => 'custom_refresh',
489
+ 'refresh_token' => tokens[:refresh_token],
490
+ 'custom_param' => 'custom_value'
491
+ )
492
+ )
493
+
494
+ if response.status != 200
495
+ raise Legate::Auth::TokenRefreshError, "Failed to refresh token: #{response.body}"
496
+ end
497
+
498
+ # Parse and return the refreshed tokens
499
+ refresh_data = parse_token_response(response.body)
500
+
501
+ # Preserve the original refresh token if a new one isn't provided
502
+ refresh_data[:refresh_token] ||= tokens[:refresh_token]
503
+
504
+ refresh_data
505
+ end
506
+ ```
507
+
508
+ ## Best Practices
509
+
510
+ 1. **Security First**: Implement proper security measures, including CSRF protection, secure token storage, and HTTPS for all requests
511
+ 2. **Error Handling**: Implement comprehensive error handling for all authentication steps
512
+ 3. **Logging**: Add appropriate logging to help debug authentication issues
513
+ 4. **Testing**: Write thorough tests for both normal and error cases
514
+ 5. **Documentation**: Document your custom authentication scheme thoroughly
515
+ 6. **Token Management**: Implement proper token lifecycle management (expiration, refresh)
516
+ 7. **Follow Standards**: When possible, follow existing standards and conventions
517
+
518
+ ## Related Topics
519
+ - [Authentication Configuration](./configuration)
520
+ - [Token Lifecycle Management](./token_lifecycle)
521
+ - [Secure Credential Storage](./secure_storage)
522
+ - [`Legate::Auth::Scheme` API Reference](../api_reference/scheme)
523
+ - [`Legate::ToolContext` Authentication Extensions](../api_reference/tool_context_extension)
@@ -0,0 +1,24 @@
1
+ # Authentication Guides
2
+
3
+ This section provides detailed guides for implementing authentication in your Legate Ruby applications.
4
+
5
+ ## Core Concepts
6
+
7
+ - [Authentication Overview](./overview) - Core concepts and general workflow
8
+ - [Authentication Configuration](./configuration) - How to configure authentication
9
+
10
+ ## Authentication Methods
11
+
12
+ - [API Key Authentication](./api_key) - Using API Keys in header or query parameters
13
+ - [HTTP Bearer Authentication](./bearer) - Using Bearer tokens
14
+ - [OAuth2 Authentication](./oauth2) - Complete OAuth2 implementation guide
15
+ - [OpenID Connect](./oidc) - Using OIDC authentication
16
+ - [Service Account Authentication](./service_account) - Using Service Accounts
17
+
18
+ ## Advanced Topics
19
+
20
+ - [Token Lifecycle Management](./token_lifecycle) - Managing token expiration and refresh
21
+ - [Custom Authentication Flow](./custom_flow) - Building custom authentication flows
22
+ - [Secure Credential Storage](./secure_storage) - Best practices for credential security
23
+ - [Web UI Integration](./web_ui_integration) - Integrating authentication with the Web UI
24
+ - [Migrating from Earlier Versions](./migration) - Guide for upgrading from previous versions