tasker-engine 1.0.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.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +443 -0
- data/Rakefile +10 -0
- data/app/controllers/tasker/analytics_controller.rb +179 -0
- data/app/controllers/tasker/application_controller.rb +45 -0
- data/app/controllers/tasker/graphql_controller.rb +193 -0
- data/app/controllers/tasker/handlers_controller.rb +217 -0
- data/app/controllers/tasker/health_controller.rb +229 -0
- data/app/controllers/tasker/metrics_controller.rb +111 -0
- data/app/controllers/tasker/page_sort.rb +97 -0
- data/app/controllers/tasker/task_diagrams_controller.rb +30 -0
- data/app/controllers/tasker/tasks_controller.rb +123 -0
- data/app/controllers/tasker/workflow_steps_controller.rb +69 -0
- data/app/graphql/examples/all_tasks.graphql +22 -0
- data/app/graphql/examples/pending_tasks.graphql +23 -0
- data/app/graphql/tasker/graph_ql_types/annotation_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_argument.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_connection.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/base_edge.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_enum.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_field.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_input_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_interface.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_scalar.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_union.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_object_map_type.rb +18 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_type.rb +13 -0
- data/app/graphql/tasker/graph_ql_types/mutation_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_step_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_task_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/named_tasks_named_step_type.rb +19 -0
- data/app/graphql/tasker/graph_ql_types/node_type.rb +12 -0
- data/app/graphql/tasker/graph_ql_types/query_type.rb +20 -0
- data/app/graphql/tasker/graph_ql_types/task_annotation_type.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_interface.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_type.rb +26 -0
- data/app/graphql/tasker/graph_ql_types/workflow_step_type.rb +154 -0
- data/app/graphql/tasker/graph_ql_types.rb +42 -0
- data/app/graphql/tasker/mutations/base_mutation.rb +13 -0
- data/app/graphql/tasker/mutations/cancel_step.rb +29 -0
- data/app/graphql/tasker/mutations/cancel_task.rb +29 -0
- data/app/graphql/tasker/mutations/create_task.rb +52 -0
- data/app/graphql/tasker/mutations/update_step.rb +36 -0
- data/app/graphql/tasker/mutations/update_task.rb +41 -0
- data/app/graphql/tasker/queries/all_annotation_types.rb +17 -0
- data/app/graphql/tasker/queries/all_tasks.rb +23 -0
- data/app/graphql/tasker/queries/base_query.rb +9 -0
- data/app/graphql/tasker/queries/helpers.rb +16 -0
- data/app/graphql/tasker/queries/one_step.rb +24 -0
- data/app/graphql/tasker/queries/one_task.rb +18 -0
- data/app/graphql/tasker/queries/tasks_by_annotation.rb +31 -0
- data/app/graphql/tasker/queries/tasks_by_status.rb +30 -0
- data/app/graphql/tasker/tasker_rails_schema.rb +52 -0
- data/app/jobs/tasker/application_job.rb +8 -0
- data/app/jobs/tasker/metrics_export_job.rb +252 -0
- data/app/jobs/tasker/task_runner_job.rb +224 -0
- data/app/models/tasker/annotation_type.rb +26 -0
- data/app/models/tasker/application_record.rb +70 -0
- data/app/models/tasker/dependent_system.rb +26 -0
- data/app/models/tasker/dependent_system_object_map.rb +64 -0
- data/app/models/tasker/diagram/edge.rb +106 -0
- data/app/models/tasker/diagram/flowchart.rb +137 -0
- data/app/models/tasker/diagram/node.rb +99 -0
- data/app/models/tasker/named_step.rb +41 -0
- data/app/models/tasker/named_task.rb +121 -0
- data/app/models/tasker/named_tasks_named_step.rb +82 -0
- data/app/models/tasker/step_dag_relationship.rb +65 -0
- data/app/models/tasker/step_readiness_status.rb +59 -0
- data/app/models/tasker/task.rb +424 -0
- data/app/models/tasker/task_annotation.rb +36 -0
- data/app/models/tasker/task_diagram.rb +332 -0
- data/app/models/tasker/task_execution_context.rb +29 -0
- data/app/models/tasker/task_namespace.rb +41 -0
- data/app/models/tasker/task_transition.rb +235 -0
- data/app/models/tasker/workflow_step.rb +461 -0
- data/app/models/tasker/workflow_step_edge.rb +94 -0
- data/app/models/tasker/workflow_step_transition.rb +434 -0
- data/app/serializers/tasker/annotation_type_serializer.rb +8 -0
- data/app/serializers/tasker/handler_serializer.rb +109 -0
- data/app/serializers/tasker/task_annotation_serializer.rb +32 -0
- data/app/serializers/tasker/task_serializer.rb +168 -0
- data/app/serializers/tasker/workflow_step_serializer.rb +27 -0
- data/app/services/tasker/analytics_service.rb +409 -0
- data/app/views/tasker/task/_diagram.html.erb +32 -0
- data/config/initializers/dry_struct.rb +11 -0
- data/config/initializers/statesman.rb +6 -0
- data/config/initializers/tasker_orchestration.rb +17 -0
- data/config/initializers/time_formats.rb +4 -0
- data/config/routes.rb +34 -0
- data/config/tasker/subscriptions/example_integrations.yml +67 -0
- data/config/tasker/system_events.yml +305 -0
- data/db/functions/calculate_dependency_levels_v01.sql +45 -0
- data/db/functions/get_analytics_metrics_v01.sql +137 -0
- data/db/functions/get_slowest_steps_v01.sql +82 -0
- data/db/functions/get_slowest_tasks_v01.sql +96 -0
- data/db/functions/get_step_readiness_status_batch_v01.sql +140 -0
- data/db/functions/get_step_readiness_status_v01.sql +139 -0
- data/db/functions/get_system_health_counts_v01.sql +108 -0
- data/db/functions/get_task_execution_context_v01.sql +108 -0
- data/db/functions/get_task_execution_contexts_batch_v01.sql +104 -0
- data/db/init/schema.sql +2277 -0
- data/db/migrate/20250701165431_initial_tasker_schema.rb +116 -0
- data/db/views/tasker_step_dag_relationships_v01.sql +69 -0
- data/docs/APPLICATION_GENERATOR.md +384 -0
- data/docs/AUTH.md +1780 -0
- data/docs/CIRCUIT_BREAKER.md +224 -0
- data/docs/DEVELOPER_GUIDE.md +2665 -0
- data/docs/EVENT_SYSTEM.md +637 -0
- data/docs/EXECUTION_CONFIGURATION.md +341 -0
- data/docs/FLOW_CHART.md +149 -0
- data/docs/HEALTH.md +542 -0
- data/docs/METRICS.md +731 -0
- data/docs/OPTIMIZATION_PLAN.md +1479 -0
- data/docs/OVERVIEW.md +552 -0
- data/docs/QUICK_START.md +270 -0
- data/docs/REGISTRY_SYSTEMS.md +373 -0
- data/docs/REST_API.md +632 -0
- data/docs/ROADMAP.md +221 -0
- data/docs/SQL_FUNCTIONS.md +1408 -0
- data/docs/TASK_DIAGRAM.md +252 -0
- data/docs/TASK_EXECUTION_CONTROL_FLOW.md +237 -0
- data/docs/TELEMETRY.md +795 -0
- data/docs/TROUBLESHOOTING.md +756 -0
- data/docs/TaskHandlerGenerator.html +255 -0
- data/docs/Tasker/Analysis/RuntimeGraphAnalyzer.html +907 -0
- data/docs/Tasker/Analysis/TemplateGraphAnalyzer.html +1236 -0
- data/docs/Tasker/Analysis.html +117 -0
- data/docs/Tasker/AnalyticsController.html +450 -0
- data/docs/Tasker/AnalyticsService/BottleneckAnalytics.html +816 -0
- data/docs/Tasker/AnalyticsService/PerformanceAnalytics.html +586 -0
- data/docs/Tasker/AnalyticsService.html +2221 -0
- data/docs/Tasker/AnnotationType.html +137 -0
- data/docs/Tasker/AnnotationTypeSerializer.html +124 -0
- data/docs/Tasker/ApplicationController.html +147 -0
- data/docs/Tasker/ApplicationJob.html +128 -0
- data/docs/Tasker/ApplicationRecord.html +378 -0
- data/docs/Tasker/Authentication/AuthenticationError.html +124 -0
- data/docs/Tasker/Authentication/ConfigurationError.html +124 -0
- data/docs/Tasker/Authentication/Coordinator.html +242 -0
- data/docs/Tasker/Authentication/Interface.html +560 -0
- data/docs/Tasker/Authentication/InterfaceError.html +124 -0
- data/docs/Tasker/Authentication/NoneAuthenticator.html +338 -0
- data/docs/Tasker/Authentication.html +119 -0
- data/docs/Tasker/Authorization/AuthorizationError.html +139 -0
- data/docs/Tasker/Authorization/BaseCoordinator.html +927 -0
- data/docs/Tasker/Authorization/ConfigurationError.html +153 -0
- data/docs/Tasker/Authorization/ResourceConstants/ACTIONS.html +428 -0
- data/docs/Tasker/Authorization/ResourceConstants/RESOURCES.html +365 -0
- data/docs/Tasker/Authorization/ResourceConstants.html +146 -0
- data/docs/Tasker/Authorization/ResourceRegistry.html +882 -0
- data/docs/Tasker/Authorization/UnauthorizedError.html +153 -0
- data/docs/Tasker/Authorization.html +582 -0
- data/docs/Tasker/CacheCapabilities.html +167 -0
- data/docs/Tasker/CacheStrategy.html +1297 -0
- data/docs/Tasker/Concerns/Authenticatable.html +116 -0
- data/docs/Tasker/Concerns/Authorizable/AdminStatusChecker.html +256 -0
- data/docs/Tasker/Concerns/Authorizable.html +816 -0
- data/docs/Tasker/Concerns/ControllerAuthorizable.html +157 -0
- data/docs/Tasker/Concerns/EventPublisher.html +4023 -0
- data/docs/Tasker/Concerns/IdempotentStateTransitions.html +806 -0
- data/docs/Tasker/Concerns/LifecycleEventHelpers.html +129 -0
- data/docs/Tasker/Concerns/OrchestrationPublisher.html +129 -0
- data/docs/Tasker/Concerns/StateMachineBase/ClassMethods.html +1075 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase/ClassMethods.html +191 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase.html +126 -0
- data/docs/Tasker/Concerns/StateMachineBase.html +153 -0
- data/docs/Tasker/Concerns/StructuredLogging.html +1413 -0
- data/docs/Tasker/Concerns.html +117 -0
- data/docs/Tasker/Configuration/AuthConfiguration.html +1023 -0
- data/docs/Tasker/Configuration/ConfigurationProxy.html +581 -0
- data/docs/Tasker/Configuration/DatabaseConfiguration.html +475 -0
- data/docs/Tasker/Configuration/EngineConfiguration.html +1265 -0
- data/docs/Tasker/Configuration/HealthConfiguration.html +791 -0
- data/docs/Tasker/Configuration/TelemetryConfiguration.html +1308 -0
- data/docs/Tasker/Configuration/TelemetryConfigurationProxy.html +388 -0
- data/docs/Tasker/Configuration.html +1669 -0
- data/docs/Tasker/ConfigurationError.html +143 -0
- data/docs/Tasker/ConfiguredTask.html +514 -0
- data/docs/Tasker/Constants/EventDefinitions.html +590 -0
- data/docs/Tasker/Constants/LifecycleEvents.html +137 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Step.html +152 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Task.html +142 -0
- data/docs/Tasker/Constants/ObservabilityEvents.html +126 -0
- data/docs/Tasker/Constants/RegistryEvents.html +285 -0
- data/docs/Tasker/Constants/StepEvents.html +177 -0
- data/docs/Tasker/Constants/TaskEvents.html +167 -0
- data/docs/Tasker/Constants/TaskExecution/ExecutionStatus.html +207 -0
- data/docs/Tasker/Constants/TaskExecution/HealthStatus.html +191 -0
- data/docs/Tasker/Constants/TaskExecution/RecommendedAction.html +207 -0
- data/docs/Tasker/Constants/TaskExecution.html +126 -0
- data/docs/Tasker/Constants/TaskFinalization/ErrorMessages.html +132 -0
- data/docs/Tasker/Constants/TaskFinalization/PendingReasons.html +207 -0
- data/docs/Tasker/Constants/TaskFinalization/ReenqueueReasons.html +239 -0
- data/docs/Tasker/Constants/TaskFinalization.html +126 -0
- data/docs/Tasker/Constants/TaskStatuses.html +223 -0
- data/docs/Tasker/Constants/TestEvents.html +163 -0
- data/docs/Tasker/Constants/WorkflowEvents.html +222 -0
- data/docs/Tasker/Constants/WorkflowStepStatuses.html +223 -0
- data/docs/Tasker/Constants.html +561 -0
- data/docs/Tasker/DependentSystem.html +137 -0
- data/docs/Tasker/DependentSystemObjectMap.html +250 -0
- data/docs/Tasker/DetectorRegistry.html +598 -0
- data/docs/Tasker/Diagram/Edge.html +1191 -0
- data/docs/Tasker/Diagram/Flowchart.html +1539 -0
- data/docs/Tasker/Diagram/Node.html +1165 -0
- data/docs/Tasker/Diagram.html +117 -0
- data/docs/Tasker/Engine.html +215 -0
- data/docs/Tasker/Error.html +139 -0
- data/docs/Tasker/Events/Bus.html +1226 -0
- data/docs/Tasker/Events/Catalog/CatalogPrinter.html +258 -0
- data/docs/Tasker/Events/Catalog/CustomEventRegistrar.html +276 -0
- data/docs/Tasker/Events/Catalog/ExamplePayloadGenerator.html +294 -0
- data/docs/Tasker/Events/Catalog.html +1291 -0
- data/docs/Tasker/Events/CustomRegistry.html +943 -0
- data/docs/Tasker/Events/DefinitionLoader.html +575 -0
- data/docs/Tasker/Events/EventPayloadBuilder/ErrorInfoExtractor.html +286 -0
- data/docs/Tasker/Events/EventPayloadBuilder/StepPayloadBuilder.html +312 -0
- data/docs/Tasker/Events/EventPayloadBuilder.html +664 -0
- data/docs/Tasker/Events/Publisher.html +365 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer/ErrorTypeClassifier.html +1128 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer.html +270 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/MetricTagsExtractor.html +266 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber.html +2556 -0
- data/docs/Tasker/Events/Subscribers/MetricsSubscriber.html +723 -0
- data/docs/Tasker/Events/Subscribers/TelemetrySubscriber.html +2251 -0
- data/docs/Tasker/Events/Subscribers.html +117 -0
- data/docs/Tasker/Events/SubscriptionLoader.html +493 -0
- data/docs/Tasker/Events.html +294 -0
- data/docs/Tasker/EventsGenerator.html +459 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics/AnalyticsMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics.html +412 -0
- data/docs/Tasker/Functions/FunctionBasedDependencyLevels.html +598 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps/SlowestStep.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks/SlowestTask.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedStepReadinessStatus.html +1457 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts/HealthMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts.html +370 -0
- data/docs/Tasker/Functions/FunctionBasedTaskExecutionContext.html +1250 -0
- data/docs/Tasker/Functions/FunctionWrapper.html +479 -0
- data/docs/Tasker/Functions.html +117 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator/UsageInstructionsFormatter.html +244 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator.html +373 -0
- data/docs/Tasker/Generators/AuthorizationCoordinatorGenerator.html +430 -0
- data/docs/Tasker/Generators/SubscriberGenerator.html +377 -0
- data/docs/Tasker/Generators/TaskHandlerGenerator.html +263 -0
- data/docs/Tasker/Generators.html +117 -0
- data/docs/Tasker/GraphQLTypes/AnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/BaseArgument.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseConnection.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseEdge.html +130 -0
- data/docs/Tasker/GraphQLTypes/BaseEnum.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseField.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInputObject.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInterface.html +116 -0
- data/docs/Tasker/GraphQLTypes/BaseObject.html +128 -0
- data/docs/Tasker/GraphQLTypes/BaseScalar.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseUnion.html +124 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemObjectMapType.html +132 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemType.html +132 -0
- data/docs/Tasker/GraphQLTypes/MutationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTaskType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTasksNamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NodeType.html +118 -0
- data/docs/Tasker/GraphQLTypes/QueryType.html +139 -0
- data/docs/Tasker/GraphQLTypes/TaskAnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/TaskInterface.html +111 -0
- data/docs/Tasker/GraphQLTypes/TaskType.html +201 -0
- data/docs/Tasker/GraphQLTypes/WorkflowStepType.html +694 -0
- data/docs/Tasker/GraphQLTypes.html +130 -0
- data/docs/Tasker/GraphqlController.html +251 -0
- data/docs/Tasker/HandlerFactory.html +1518 -0
- data/docs/Tasker/HandlerSerializer.html +682 -0
- data/docs/Tasker/HandlersController.html +574 -0
- data/docs/Tasker/HashIdentityStrategy.html +278 -0
- data/docs/Tasker/Health/ReadinessChecker.html +712 -0
- data/docs/Tasker/Health/StatusChecker.html +653 -0
- data/docs/Tasker/Health.html +117 -0
- data/docs/Tasker/HealthController.html +523 -0
- data/docs/Tasker/IdentityStrategy.html +276 -0
- data/docs/Tasker/InvalidTaskHandlerConfig.html +135 -0
- data/docs/Tasker/LifecycleEvents/Events/Step.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events/Task.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events.html +204 -0
- data/docs/Tasker/LifecycleEvents/Publisher.html +132 -0
- data/docs/Tasker/LifecycleEvents.html +799 -0
- data/docs/Tasker/Logging/CorrelationIdGenerator.html +688 -0
- data/docs/Tasker/Logging.html +115 -0
- data/docs/Tasker/MetricsController.html +293 -0
- data/docs/Tasker/MetricsExportJob.html +414 -0
- data/docs/Tasker/Mutations/BaseMutation.html +128 -0
- data/docs/Tasker/Mutations/CancelStep.html +219 -0
- data/docs/Tasker/Mutations/CancelTask.html +221 -0
- data/docs/Tasker/Mutations/CreateTask.html +243 -0
- data/docs/Tasker/Mutations/UpdateStep.html +243 -0
- data/docs/Tasker/Mutations/UpdateTask.html +243 -0
- data/docs/Tasker/Mutations.html +117 -0
- data/docs/Tasker/NamedStep.html +216 -0
- data/docs/Tasker/NamedTask.html +910 -0
- data/docs/Tasker/NamedTasksNamedStep.html +435 -0
- data/docs/Tasker/Orchestration/BackoffCalculator.html +404 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder/ConfigValidator.html +258 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder.html +435 -0
- data/docs/Tasker/Orchestration/ConnectionPoolIntelligence.html +513 -0
- data/docs/Tasker/Orchestration/Coordinator.html +641 -0
- data/docs/Tasker/Orchestration/FutureStateAnalyzer.html +1045 -0
- data/docs/Tasker/Orchestration/Orchestrator.html +679 -0
- data/docs/Tasker/Orchestration/PluginIntegration.html +1127 -0
- data/docs/Tasker/Orchestration/ResponseProcessor.html +504 -0
- data/docs/Tasker/Orchestration/RetryHeaderParser.html +304 -0
- data/docs/Tasker/Orchestration/StepExecutor.html +995 -0
- data/docs/Tasker/Orchestration/StepSequenceFactory.html +644 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/BlockageChecker.html +264 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ContextManager.html +254 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/DelayCalculator.html +556 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationDecisionMaker.html +348 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationProcessor.html +286 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReasonDeterminer.html +432 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReenqueueManager.html +296 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/UnclearStateHandler.html +314 -0
- data/docs/Tasker/Orchestration/TaskFinalizer.html +1212 -0
- data/docs/Tasker/Orchestration/TaskInitializer.html +766 -0
- data/docs/Tasker/Orchestration/TaskReenqueuer.html +506 -0
- data/docs/Tasker/Orchestration/ViableStepDiscovery.html +442 -0
- data/docs/Tasker/Orchestration/WorkflowCoordinator.html +510 -0
- data/docs/Tasker/Orchestration.html +130 -0
- data/docs/Tasker/PageSort/PageSortParamsBuilder.html +296 -0
- data/docs/Tasker/PageSort.html +247 -0
- data/docs/Tasker/PermanentError.html +518 -0
- data/docs/Tasker/ProceduralError.html +147 -0
- data/docs/Tasker/Queries/AllAnnotationTypes.html +217 -0
- data/docs/Tasker/Queries/AllTasks.html +221 -0
- data/docs/Tasker/Queries/BaseQuery.html +128 -0
- data/docs/Tasker/Queries/Helpers.html +187 -0
- data/docs/Tasker/Queries/OneStep.html +225 -0
- data/docs/Tasker/Queries/OneTask.html +217 -0
- data/docs/Tasker/Queries/TasksByAnnotation.html +231 -0
- data/docs/Tasker/Queries/TasksByStatus.html +233 -0
- data/docs/Tasker/Queries.html +119 -0
- data/docs/Tasker/Railtie.html +124 -0
- data/docs/Tasker/Registry/BaseRegistry.html +1690 -0
- data/docs/Tasker/Registry/EventPublisher.html +667 -0
- data/docs/Tasker/Registry/InterfaceValidator.html +569 -0
- data/docs/Tasker/Registry/RegistrationError.html +132 -0
- data/docs/Tasker/Registry/RegistryError.html +139 -0
- data/docs/Tasker/Registry/StatisticsCollector.html +841 -0
- data/docs/Tasker/Registry/SubscriberRegistry.html +1504 -0
- data/docs/Tasker/Registry/ValidationError.html +132 -0
- data/docs/Tasker/Registry.html +119 -0
- data/docs/Tasker/RetryableError.html +515 -0
- data/docs/Tasker/StateMachine/Compatibility.html +282 -0
- data/docs/Tasker/StateMachine/InvalidStateTransition.html +135 -0
- data/docs/Tasker/StateMachine/StepStateMachine/StandardizedPayloadBuilder.html +260 -0
- data/docs/Tasker/StateMachine/StepStateMachine.html +2215 -0
- data/docs/Tasker/StateMachine/TaskStateMachine.html +734 -0
- data/docs/Tasker/StateMachine.html +602 -0
- data/docs/Tasker/StepDagRelationship.html +657 -0
- data/docs/Tasker/StepHandler/Api/Config.html +1091 -0
- data/docs/Tasker/StepHandler/Api.html +884 -0
- data/docs/Tasker/StepHandler/AutomaticEventPublishing.html +321 -0
- data/docs/Tasker/StepHandler/Base.html +970 -0
- data/docs/Tasker/StepHandler.html +119 -0
- data/docs/Tasker/StepReadinessStatus.html +836 -0
- data/docs/Tasker/Task.html +2575 -0
- data/docs/Tasker/TaskAnnotation.html +137 -0
- data/docs/Tasker/TaskAnnotationSerializer.html +124 -0
- data/docs/Tasker/TaskBuilder/StepNameValidator.html +264 -0
- data/docs/Tasker/TaskBuilder/StepTemplateDefiner.html +264 -0
- data/docs/Tasker/TaskBuilder.html +764 -0
- data/docs/Tasker/TaskDiagram/StepToStepEdgeBuilder.html +260 -0
- data/docs/Tasker/TaskDiagram/TaskToRootStepEdgeBuilder.html +290 -0
- data/docs/Tasker/TaskDiagram.html +548 -0
- data/docs/Tasker/TaskDiagramsController.html +240 -0
- data/docs/Tasker/TaskExecutionContext.html +469 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/ClassBasedEventRegistrar.html +238 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/YamlEventRegistrar.html +254 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner.html +988 -0
- data/docs/Tasker/TaskHandler/ClassMethods.html +357 -0
- data/docs/Tasker/TaskHandler/InstanceMethods.html +1396 -0
- data/docs/Tasker/TaskHandler/StepGroup.html +1748 -0
- data/docs/Tasker/TaskHandler.html +271 -0
- data/docs/Tasker/TaskNamespace.html +312 -0
- data/docs/Tasker/TaskRunnerJob.html +406 -0
- data/docs/Tasker/TaskSerializer.html +474 -0
- data/docs/Tasker/TaskTransition.html +1517 -0
- data/docs/Tasker/TaskWorkflowSummary.html +988 -0
- data/docs/Tasker/TaskerRailsSchema/InvalidObjectTypeError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema/TypeResolutionError.html +139 -0
- data/docs/Tasker/TaskerRailsSchema/UnknownInterfaceError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema.html +384 -0
- data/docs/Tasker/TasksController.html +595 -0
- data/docs/Tasker/Telemetry/EventMapping.html +1307 -0
- data/docs/Tasker/Telemetry/EventRouter.html +2178 -0
- data/docs/Tasker/Telemetry/Events/ExportEvents.html +246 -0
- data/docs/Tasker/Telemetry/Events.html +115 -0
- data/docs/Tasker/Telemetry/ExportCoordinator/DistributedLockTimeoutError.html +135 -0
- data/docs/Tasker/Telemetry/ExportCoordinator.html +2137 -0
- data/docs/Tasker/Telemetry/IntelligentCacheManager.html +1083 -0
- data/docs/Tasker/Telemetry/LogBackend.html +1088 -0
- data/docs/Tasker/Telemetry/MetricTypes/Counter.html +1054 -0
- data/docs/Tasker/Telemetry/MetricTypes/Gauge.html +1270 -0
- data/docs/Tasker/Telemetry/MetricTypes/Histogram.html +1492 -0
- data/docs/Tasker/Telemetry/MetricTypes.html +153 -0
- data/docs/Tasker/Telemetry/MetricsBackend.html +2510 -0
- data/docs/Tasker/Telemetry/MetricsExportService.html +578 -0
- data/docs/Tasker/Telemetry/PluginRegistry.html +1774 -0
- data/docs/Tasker/Telemetry/Plugins/BaseExporter.html +1835 -0
- data/docs/Tasker/Telemetry/Plugins/CsvExporter.html +768 -0
- data/docs/Tasker/Telemetry/Plugins/JsonExporter.html +747 -0
- data/docs/Tasker/Telemetry/Plugins.html +117 -0
- data/docs/Tasker/Telemetry/PrometheusExporter.html +481 -0
- data/docs/Tasker/Telemetry/TraceBackend.html +891 -0
- data/docs/Tasker/Telemetry.html +130 -0
- data/docs/Tasker/Types/AuthConfig.html +886 -0
- data/docs/Tasker/Types/BackoffConfig.html +1063 -0
- data/docs/Tasker/Types/BaseConfig.html +227 -0
- data/docs/Tasker/Types/CacheConfig.html +1731 -0
- data/docs/Tasker/Types/DatabaseConfig.html +388 -0
- data/docs/Tasker/Types/DependencyGraph.html +526 -0
- data/docs/Tasker/Types/DependencyGraphConfig.html +753 -0
- data/docs/Tasker/Types/EngineConfig.html +1181 -0
- data/docs/Tasker/Types/ExecutionConfig.html +1963 -0
- data/docs/Tasker/Types/GraphEdge.html +517 -0
- data/docs/Tasker/Types/GraphMetadata.html +781 -0
- data/docs/Tasker/Types/GraphNode.html +694 -0
- data/docs/Tasker/Types/HealthConfig.html +784 -0
- data/docs/Tasker/Types/StepSequence.html +353 -0
- data/docs/Tasker/Types/StepTemplate.html +1193 -0
- data/docs/Tasker/Types/TaskRequest.html +1179 -0
- data/docs/Tasker/Types/TelemetryConfig.html +2746 -0
- data/docs/Tasker/Types.html +154 -0
- data/docs/Tasker/WorkflowStep/StepFinder.html +282 -0
- data/docs/Tasker/WorkflowStep.html +2724 -0
- data/docs/Tasker/WorkflowStepEdge.html +304 -0
- data/docs/Tasker/WorkflowStepSerializer.html +305 -0
- data/docs/Tasker/WorkflowStepTransition/TransitionDescriptionFormatter.html +282 -0
- data/docs/Tasker/WorkflowStepTransition.html +2201 -0
- data/docs/Tasker/WorkflowStepsController.html +462 -0
- data/docs/Tasker.html +452 -0
- data/docs/VISION.md +584 -0
- data/docs/WHY.md +21 -0
- data/docs/_index.html +2375 -0
- data/docs/class_list.html +54 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +503 -0
- data/docs/events/migration_plan_outcomes.md +80 -0
- data/docs/file.README.html +541 -0
- data/docs/file_list.html +59 -0
- data/docs/frames.html +22 -0
- data/docs/index.html +541 -0
- data/docs/js/app.js +344 -0
- data/docs/js/full_list.js +242 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +9182 -0
- data/docs/top-level-namespace.html +110 -0
- data/lib/generators/tasker/authenticator_generator.rb +301 -0
- data/lib/generators/tasker/authorization_coordinator_generator.rb +139 -0
- data/lib/generators/tasker/events_generator.rb +91 -0
- data/lib/generators/tasker/subscriber_generator.rb +107 -0
- data/lib/generators/tasker/task_handler_generator.rb +138 -0
- data/lib/generators/tasker/templates/api_token_authenticator.rb.erb +113 -0
- data/lib/generators/tasker/templates/api_token_authenticator_spec.rb.erb +144 -0
- data/lib/generators/tasker/templates/authorization_coordinator.rb.erb +95 -0
- data/lib/generators/tasker/templates/authorization_coordinator_spec.rb.erb +142 -0
- data/lib/generators/tasker/templates/custom_authenticator.rb.erb +108 -0
- data/lib/generators/tasker/templates/custom_authenticator_spec.rb.erb +162 -0
- data/lib/generators/tasker/templates/custom_events.yml.erb +62 -0
- data/lib/generators/tasker/templates/custom_subscriber.rb.erb +72 -0
- data/lib/generators/tasker/templates/devise_authenticator.rb.erb +101 -0
- data/lib/generators/tasker/templates/devise_authenticator_spec.rb.erb +126 -0
- data/lib/generators/tasker/templates/initialize.rb.erb +202 -0
- data/lib/generators/tasker/templates/jwt_authenticator.rb.erb +144 -0
- data/lib/generators/tasker/templates/jwt_authenticator_spec.rb.erb +298 -0
- data/lib/generators/tasker/templates/metrics_subscriber.rb.erb +258 -0
- data/lib/generators/tasker/templates/metrics_subscriber_spec.rb.erb +308 -0
- data/lib/generators/tasker/templates/omniauth_authenticator.rb.erb +135 -0
- data/lib/generators/tasker/templates/omniauth_authenticator_spec.rb.erb +196 -0
- data/lib/generators/tasker/templates/opentelemetry_initializer.rb +52 -0
- data/lib/generators/tasker/templates/subscriber.rb.erb +64 -0
- data/lib/generators/tasker/templates/subscriber_spec.rb.erb +80 -0
- data/lib/generators/tasker/templates/task_config.yaml.erb +117 -0
- data/lib/generators/tasker/templates/task_handler.rb.erb +59 -0
- data/lib/generators/tasker/templates/task_handler_spec.rb.erb +159 -0
- data/lib/tasker/analysis/runtime_graph_analyzer.rb +1168 -0
- data/lib/tasker/analysis/template_graph_analyzer.rb +328 -0
- data/lib/tasker/authentication/coordinator.rb +78 -0
- data/lib/tasker/authentication/errors.rb +9 -0
- data/lib/tasker/authentication/interface.rb +36 -0
- data/lib/tasker/authentication/none_authenticator.rb +26 -0
- data/lib/tasker/authorization/base_coordinator.rb +112 -0
- data/lib/tasker/authorization/errors.rb +26 -0
- data/lib/tasker/authorization/resource_constants.rb +74 -0
- data/lib/tasker/authorization/resource_registry.rb +143 -0
- data/lib/tasker/authorization.rb +75 -0
- data/lib/tasker/cache_capabilities.rb +131 -0
- data/lib/tasker/cache_strategy.rb +469 -0
- data/lib/tasker/concerns/authenticatable.rb +41 -0
- data/lib/tasker/concerns/authorizable.rb +204 -0
- data/lib/tasker/concerns/controller_authorizable.rb +124 -0
- data/lib/tasker/concerns/event_publisher.rb +716 -0
- data/lib/tasker/concerns/idempotent_state_transitions.rb +128 -0
- data/lib/tasker/concerns/state_machine_base.rb +218 -0
- data/lib/tasker/concerns/structured_logging.rb +387 -0
- data/lib/tasker/configuration.rb +325 -0
- data/lib/tasker/constants/event_definitions.rb +147 -0
- data/lib/tasker/constants/registry_events.rb +54 -0
- data/lib/tasker/constants.rb +417 -0
- data/lib/tasker/engine.rb +90 -0
- data/lib/tasker/errors.rb +90 -0
- data/lib/tasker/events/catalog.rb +432 -0
- data/lib/tasker/events/custom_registry.rb +175 -0
- data/lib/tasker/events/definition_loader.rb +199 -0
- data/lib/tasker/events/event_payload_builder.rb +461 -0
- data/lib/tasker/events/publisher.rb +149 -0
- data/lib/tasker/events/subscribers/base_subscriber.rb +601 -0
- data/lib/tasker/events/subscribers/metrics_subscriber.rb +120 -0
- data/lib/tasker/events/subscribers/telemetry_subscriber.rb +462 -0
- data/lib/tasker/events/subscription_loader.rb +161 -0
- data/lib/tasker/events.rb +37 -0
- data/lib/tasker/functions/function_based_analytics_metrics.rb +103 -0
- data/lib/tasker/functions/function_based_dependency_levels.rb +54 -0
- data/lib/tasker/functions/function_based_slowest_steps.rb +84 -0
- data/lib/tasker/functions/function_based_slowest_tasks.rb +84 -0
- data/lib/tasker/functions/function_based_step_readiness_status.rb +183 -0
- data/lib/tasker/functions/function_based_system_health_counts.rb +94 -0
- data/lib/tasker/functions/function_based_task_execution_context.rb +148 -0
- data/lib/tasker/functions/function_wrapper.rb +42 -0
- data/lib/tasker/functions.rb +12 -0
- data/lib/tasker/handler_factory.rb +322 -0
- data/lib/tasker/health/readiness_checker.rb +186 -0
- data/lib/tasker/health/status_checker.rb +203 -0
- data/lib/tasker/identity_strategy.rb +38 -0
- data/lib/tasker/logging/correlation_id_generator.rb +120 -0
- data/lib/tasker/orchestration/backoff_calculator.rb +184 -0
- data/lib/tasker/orchestration/connection_builder.rb +122 -0
- data/lib/tasker/orchestration/connection_pool_intelligence.rb +177 -0
- data/lib/tasker/orchestration/coordinator.rb +119 -0
- data/lib/tasker/orchestration/future_state_analyzer.rb +137 -0
- data/lib/tasker/orchestration/plugin_integration.rb +124 -0
- data/lib/tasker/orchestration/response_processor.rb +168 -0
- data/lib/tasker/orchestration/retry_header_parser.rb +78 -0
- data/lib/tasker/orchestration/step_executor.rb +941 -0
- data/lib/tasker/orchestration/step_sequence_factory.rb +67 -0
- data/lib/tasker/orchestration/task_finalizer.rb +564 -0
- data/lib/tasker/orchestration/task_initializer.rb +140 -0
- data/lib/tasker/orchestration/task_reenqueuer.rb +71 -0
- data/lib/tasker/orchestration/viable_step_discovery.rb +65 -0
- data/lib/tasker/orchestration/workflow_coordinator.rb +294 -0
- data/lib/tasker/orchestration.rb +45 -0
- data/lib/tasker/railtie.rb +9 -0
- data/lib/tasker/registry/base_registry.rb +177 -0
- data/lib/tasker/registry/event_publisher.rb +91 -0
- data/lib/tasker/registry/interface_validator.rb +140 -0
- data/lib/tasker/registry/statistics_collector.rb +381 -0
- data/lib/tasker/registry/subscriber_registry.rb +285 -0
- data/lib/tasker/registry.rb +22 -0
- data/lib/tasker/state_machine/step_state_machine.rb +508 -0
- data/lib/tasker/state_machine/task_state_machine.rb +192 -0
- data/lib/tasker/state_machine.rb +83 -0
- data/lib/tasker/step_handler/api.rb +410 -0
- data/lib/tasker/step_handler/base.rb +206 -0
- data/lib/tasker/task_builder.rb +432 -0
- data/lib/tasker/task_handler/class_methods.rb +324 -0
- data/lib/tasker/task_handler/instance_methods.rb +293 -0
- data/lib/tasker/task_handler/step_group.rb +182 -0
- data/lib/tasker/task_handler.rb +43 -0
- data/lib/tasker/telemetry/event_mapping.rb +126 -0
- data/lib/tasker/telemetry/event_router.rb +318 -0
- data/lib/tasker/telemetry/events/export_events.rb +38 -0
- data/lib/tasker/telemetry/export_coordinator.rb +497 -0
- data/lib/tasker/telemetry/intelligent_cache_manager.rb +508 -0
- data/lib/tasker/telemetry/log_backend.rb +224 -0
- data/lib/tasker/telemetry/metric_types.rb +368 -0
- data/lib/tasker/telemetry/metrics_backend.rb +1227 -0
- data/lib/tasker/telemetry/metrics_export_service.rb +392 -0
- data/lib/tasker/telemetry/plugin_registry.rb +333 -0
- data/lib/tasker/telemetry/plugins/base_exporter.rb +246 -0
- data/lib/tasker/telemetry/plugins/csv_exporter.rb +198 -0
- data/lib/tasker/telemetry/plugins/json_exporter.rb +141 -0
- data/lib/tasker/telemetry/prometheus_exporter.rb +249 -0
- data/lib/tasker/telemetry/trace_backend.rb +186 -0
- data/lib/tasker/telemetry.rb +59 -0
- data/lib/tasker/types/auth_config.rb +81 -0
- data/lib/tasker/types/backoff_config.rb +142 -0
- data/lib/tasker/types/cache_config.rb +257 -0
- data/lib/tasker/types/database_config.rb +39 -0
- data/lib/tasker/types/dependency_graph.rb +225 -0
- data/lib/tasker/types/dependency_graph_config.rb +149 -0
- data/lib/tasker/types/engine_config.rb +131 -0
- data/lib/tasker/types/execution_config.rb +289 -0
- data/lib/tasker/types/health_config.rb +84 -0
- data/lib/tasker/types/step_sequence.rb +24 -0
- data/lib/tasker/types/step_template.rb +63 -0
- data/lib/tasker/types/task_request.rb +60 -0
- data/lib/tasker/types/telemetry_config.rb +273 -0
- data/lib/tasker/types.rb +64 -0
- data/lib/tasker/version.rb +7 -0
- data/lib/tasker.rb +82 -0
- data/lib/tasks/tasker_tasks.rake +302 -0
- metadata +958 -0
data/docs/AUTH.md
ADDED
@@ -0,0 +1,1780 @@
|
|
1
|
+
# Tasker Authentication & Authorization Guide
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Tasker provides a comprehensive, flexible authentication and authorization system that works with any Rails authentication solution. The system uses **dependency injection** and **resource-based authorization** to allow host applications to provide their own authentication logic while maintaining enterprise-grade security for both REST APIs and GraphQL endpoints.
|
6
|
+
|
7
|
+
## 🎉 Latest Updates - Complete Authorization System
|
8
|
+
|
9
|
+
**Phase 5 Controller Integration - COMPLETED! ✅**
|
10
|
+
|
11
|
+
We've successfully implemented **revolutionary GraphQL authorization** and complete controller integration:
|
12
|
+
|
13
|
+
- **✅ GraphQL Operation-Level Authorization**: Automatically maps GraphQL operations to resource:action permissions
|
14
|
+
- **✅ Automatic Controller Authorization**: All REST and GraphQL endpoints protected seamlessly
|
15
|
+
- **✅ Resource Registry**: Centralized constants eliminate hardcoded strings throughout codebase
|
16
|
+
- **✅ Complete Test Coverage**: 674/674 tests passing with comprehensive integration testing
|
17
|
+
- **✅ Zero Breaking Changes**: All features are opt-in and backward compatible
|
18
|
+
- **✅ State Isolation**: Robust test infrastructure prevents configuration leakage
|
19
|
+
|
20
|
+
**Ready for Production**: The complete authentication and authorization system is now production-ready with enterprise-grade security for both REST APIs and GraphQL endpoints.
|
21
|
+
|
22
|
+
## Key Benefits
|
23
|
+
|
24
|
+
- **Provider Agnostic**: Works with Devise, JWT, OmniAuth, custom authentication, or no authentication
|
25
|
+
- **Dependency Injection**: Host applications implement authenticators rather than building provider-specific code into Tasker
|
26
|
+
- **Resource-Based Authorization**: Granular permissions using resource:action patterns (e.g., `tasker.task:create`)
|
27
|
+
- **GraphQL Operation-Level Authorization**: Revolutionary security for GraphQL that maps operations to resource permissions
|
28
|
+
- **Automatic Controller Integration**: Authentication and authorization work seamlessly across REST and GraphQL
|
29
|
+
- **Interface Validation**: Ensures authenticators implement required methods with helpful error messages
|
30
|
+
- **Configuration Validation**: Built-in validation with security best practices
|
31
|
+
- **Flexible Configuration**: Support for multiple strategies and environment-specific settings
|
32
|
+
- **Zero Breaking Changes**: All features are opt-in and backward compatible
|
33
|
+
|
34
|
+
## Table of Contents
|
35
|
+
|
36
|
+
- [Quick Start](#quick-start)
|
37
|
+
- [Authorization Quick Start](#authorization-quick-start)
|
38
|
+
- [GraphQL Authorization](#graphql-authorization)
|
39
|
+
- [Authenticator Generator](#authenticator-generator)
|
40
|
+
- [Configuration Options](#configuration-options)
|
41
|
+
- [Authentication Strategies](#authentication-strategies)
|
42
|
+
- [Authorization System](#authorization-system)
|
43
|
+
- [Building Custom Authenticators](#building-custom-authenticators)
|
44
|
+
- [Building Authorization Coordinators](#building-authorization-coordinators)
|
45
|
+
- [JWT Authentication Example](#jwt-authentication-example)
|
46
|
+
- [Integration with Controllers](#integration-with-controllers)
|
47
|
+
- [Error Handling](#error-handling)
|
48
|
+
- [Testing Authentication](#testing-authentication)
|
49
|
+
- [Testing Authorization](#testing-authorization)
|
50
|
+
- [Best Practices](#best-practices)
|
51
|
+
|
52
|
+
## Quick Start
|
53
|
+
|
54
|
+
### 1. No Authentication (Default)
|
55
|
+
|
56
|
+
By default, Tasker requires no authentication:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
# config/initializers/tasker.rb
|
60
|
+
Tasker.configuration do |config|
|
61
|
+
config.auth do |auth|
|
62
|
+
auth.authentication_enabled = false # Default - no configuration needed
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
### 2. Custom Authentication
|
68
|
+
|
69
|
+
For any authentication system, enable authentication and specify your authenticator class:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
# config/initializers/tasker.rb
|
73
|
+
Tasker.configuration do |config|
|
74
|
+
config.auth do |auth|
|
75
|
+
auth.authentication_enabled = true
|
76
|
+
auth.authenticator_class = 'YourCustomAuthenticator'
|
77
|
+
# Additional options specific to your authenticator can be passed to the authenticator
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
### 3. With Authorization (Recommended)
|
83
|
+
|
84
|
+
Enable both authentication and authorization for complete security:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
# config/initializers/tasker.rb
|
88
|
+
Tasker.configuration do |config|
|
89
|
+
config.auth do |auth|
|
90
|
+
auth.authentication_enabled = true
|
91
|
+
auth.authenticator_class = 'YourCustomAuthenticator'
|
92
|
+
auth.authorization_enabled = true
|
93
|
+
auth.authorization_coordinator_class = 'YourAuthorizationCoordinator'
|
94
|
+
auth.user_class = 'User'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
## Authorization Quick Start
|
100
|
+
|
101
|
+
Authorization in Tasker uses a **resource:action** permission model that works seamlessly with both REST APIs and GraphQL.
|
102
|
+
|
103
|
+
### 1. Enable Authorization
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
# config/initializers/tasker.rb
|
107
|
+
Tasker.configuration do |config|
|
108
|
+
config.auth do |auth|
|
109
|
+
auth.authorization_enabled = true
|
110
|
+
auth.authorization_coordinator_class = 'YourAuthorizationCoordinator'
|
111
|
+
auth.user_class = 'User'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
### 2. Create Authorization Coordinator
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
# app/tasker/authorization/your_authorization_coordinator.rb
|
120
|
+
class YourAuthorizationCoordinator < Tasker::Authorization::BaseCoordinator
|
121
|
+
protected
|
122
|
+
|
123
|
+
def authorized?(resource, action, context = {})
|
124
|
+
case resource
|
125
|
+
when Tasker::Authorization::ResourceConstants::RESOURCES::TASK
|
126
|
+
authorize_task_action(action, context)
|
127
|
+
when Tasker::Authorization::ResourceConstants::RESOURCES::WORKFLOW_STEP
|
128
|
+
authorize_step_action(action, context)
|
129
|
+
when Tasker::Authorization::ResourceConstants::RESOURCES::TASK_DIAGRAM
|
130
|
+
authorize_diagram_action(action, context)
|
131
|
+
when Tasker::Authorization::ResourceConstants::RESOURCES::HEALTH_STATUS
|
132
|
+
authorize_health_status_action(action, context)
|
133
|
+
else
|
134
|
+
false
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def authorize_task_action(action, context)
|
141
|
+
case action
|
142
|
+
when :index, :show
|
143
|
+
# Regular users can view tasks
|
144
|
+
user.tasker_admin? || user.has_tasker_permission?("#{Tasker::Authorization::ResourceConstants::RESOURCES::TASK}:#{action}")
|
145
|
+
when :create, :update, :destroy, :retry, :cancel
|
146
|
+
# Only admins can modify tasks
|
147
|
+
user.tasker_admin? || user.has_tasker_permission?("#{Tasker::Authorization::ResourceConstants::RESOURCES::TASK}:#{action}")
|
148
|
+
else
|
149
|
+
false
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def authorize_step_action(action, context)
|
154
|
+
case action
|
155
|
+
when :index, :show
|
156
|
+
user.tasker_admin? || user.has_tasker_permission?("#{Tasker::Authorization::ResourceConstants::RESOURCES::WORKFLOW_STEP}:#{action}")
|
157
|
+
when :update, :destroy, :retry, :cancel
|
158
|
+
# Step modifications require admin access
|
159
|
+
user.tasker_admin?
|
160
|
+
else
|
161
|
+
false
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def authorize_diagram_action(action, context)
|
166
|
+
case action
|
167
|
+
when :index, :show
|
168
|
+
user.tasker_admin? || user.has_tasker_permission?("#{Tasker::Authorization::ResourceConstants::RESOURCES::TASK_DIAGRAM}:#{action}")
|
169
|
+
else
|
170
|
+
false
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def authorize_health_status_action(action, context)
|
175
|
+
case action
|
176
|
+
when :index
|
177
|
+
# Health status access: admin users or explicit permission
|
178
|
+
user.tasker_admin? || user.has_tasker_permission?("#{Tasker::Authorization::ResourceConstants::RESOURCES::HEALTH_STATUS}:#{action}")
|
179
|
+
else
|
180
|
+
false
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
### 3. Add Authorization to User Model
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
# app/models/user.rb
|
190
|
+
class User < ApplicationRecord
|
191
|
+
include Tasker::Concerns::Authorizable
|
192
|
+
|
193
|
+
def has_tasker_permission?(permission)
|
194
|
+
# Your permission checking logic
|
195
|
+
permissions.include?(permission)
|
196
|
+
end
|
197
|
+
|
198
|
+
def tasker_admin?
|
199
|
+
# Your admin checking logic
|
200
|
+
role == 'admin' || roles.include?('admin')
|
201
|
+
end
|
202
|
+
end
|
203
|
+
```
|
204
|
+
|
205
|
+
### 4. Automatic Protection
|
206
|
+
|
207
|
+
With authorization enabled, **all Tasker endpoints are automatically protected**:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
# REST API calls now require proper permissions
|
211
|
+
GET /tasker/tasks # Requires tasker.task:index permission
|
212
|
+
POST /tasker/tasks # Requires tasker.task:create permission
|
213
|
+
GET /tasker/tasks/123 # Requires tasker.task:show permission
|
214
|
+
|
215
|
+
# Health endpoints with optional authorization
|
216
|
+
GET /tasker/health/ready # Never requires authorization (K8s compatibility)
|
217
|
+
GET /tasker/health/live # Never requires authorization (K8s compatibility)
|
218
|
+
GET /tasker/health/status # Requires tasker.health_status:index permission (if enabled)
|
219
|
+
|
220
|
+
# GraphQL operations are automatically mapped to permissions
|
221
|
+
query { tasks { taskId } } # Requires tasker.task:index
|
222
|
+
mutation { createTask(input: {...}) { ... } } # Requires tasker.task:create
|
223
|
+
```
|
224
|
+
|
225
|
+
## Health Status Authorization
|
226
|
+
|
227
|
+
Tasker provides optional authorization for health monitoring endpoints, designed for production security while maintaining Kubernetes compatibility:
|
228
|
+
|
229
|
+
### Health Endpoint Security Model
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
# Kubernetes-compatible endpoints (never require authorization)
|
233
|
+
GET /tasker/health/ready # Always accessible - K8s readiness probe
|
234
|
+
GET /tasker/health/live # Always accessible - K8s liveness probe
|
235
|
+
|
236
|
+
# Status endpoint with optional authorization
|
237
|
+
GET /tasker/health/status # Requires tasker.health_status:index permission (if enabled)
|
238
|
+
```
|
239
|
+
|
240
|
+
### Configuration
|
241
|
+
|
242
|
+
Enable health status authorization:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
# config/initializers/tasker.rb
|
246
|
+
Tasker.configuration do |config|
|
247
|
+
config.auth do |auth|
|
248
|
+
auth.authorization_enabled = true
|
249
|
+
auth.authorization_coordinator_class = 'YourAuthorizationCoordinator'
|
250
|
+
end
|
251
|
+
|
252
|
+
config.health do |health|
|
253
|
+
health.status_requires_authentication = true # Optional authentication
|
254
|
+
end
|
255
|
+
end
|
256
|
+
```
|
257
|
+
|
258
|
+
### Authorization Implementation
|
259
|
+
|
260
|
+
Add health status authorization to your coordinator:
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
class YourAuthorizationCoordinator < Tasker::Authorization::BaseCoordinator
|
264
|
+
include Tasker::Authorization::ResourceConstants
|
265
|
+
|
266
|
+
protected
|
267
|
+
|
268
|
+
def authorized?(resource, action, context = {})
|
269
|
+
case resource
|
270
|
+
when RESOURCES::HEALTH_STATUS
|
271
|
+
authorize_health_status_action(action, context)
|
272
|
+
# ... other resources
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
def authorize_health_status_action(action, context)
|
279
|
+
case action
|
280
|
+
when :index
|
281
|
+
# Admin users always have access
|
282
|
+
user.tasker_admin? ||
|
283
|
+
# Regular users need explicit permission
|
284
|
+
user.has_tasker_permission?("#{RESOURCES::HEALTH_STATUS}:#{action}")
|
285
|
+
else
|
286
|
+
false
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
```
|
291
|
+
|
292
|
+
### Security Benefits
|
293
|
+
|
294
|
+
- **K8s Compatibility**: Ready/live endpoints never require authorization
|
295
|
+
- **Granular Control**: Status endpoint uses specific `health_status:index` permission
|
296
|
+
- **Admin Override**: Admin users always have health status access
|
297
|
+
- **Optional Authentication**: Can require authentication without authorization
|
298
|
+
- **Production Ready**: Designed for enterprise security requirements
|
299
|
+
|
300
|
+
For complete health monitoring documentation, see **[Health Monitoring Guide](HEALTH.md)**.
|
301
|
+
|
302
|
+
## GraphQL Authorization
|
303
|
+
|
304
|
+
Tasker provides **revolutionary GraphQL authorization** that automatically maps GraphQL operations to resource:action permissions.
|
305
|
+
|
306
|
+
### How It Works
|
307
|
+
|
308
|
+
The system **parses GraphQL queries and mutations** to extract the underlying operations, then checks permissions for each operation:
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
# This GraphQL query:
|
312
|
+
query {
|
313
|
+
tasks {
|
314
|
+
taskId
|
315
|
+
status
|
316
|
+
workflowSteps {
|
317
|
+
workflowStepId
|
318
|
+
status
|
319
|
+
}
|
320
|
+
}
|
321
|
+
}
|
322
|
+
|
323
|
+
# Is automatically mapped to these permission checks:
|
324
|
+
# - tasker.task:index (for tasks query)
|
325
|
+
# - tasker.workflow_step:index (for workflowSteps query)
|
326
|
+
```
|
327
|
+
|
328
|
+
### GraphQL Operation Mapping
|
329
|
+
|
330
|
+
| GraphQL Operation | Resource:Action Permission |
|
331
|
+
|------------------|---------------------------|
|
332
|
+
| `query { tasks }` | `tasker.task:index` |
|
333
|
+
| `query { task(taskId: "123") }` | `tasker.task:show` |
|
334
|
+
| `mutation { createTask(...) }` | `tasker.task:create` |
|
335
|
+
| `mutation { updateTask(...) }` | `tasker.task:update` |
|
336
|
+
| `mutation { cancelTask(...) }` | `tasker.task:cancel` |
|
337
|
+
| `query { step(...) }` | `tasker.workflow_step:show` |
|
338
|
+
| `mutation { updateStep(...) }` | `tasker.workflow_step:update` |
|
339
|
+
| `mutation { cancelStep(...) }` | `tasker.workflow_step:cancel` |
|
340
|
+
|
341
|
+
### GraphQL Authorization Examples
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
# Admin user - Full access
|
345
|
+
admin_user.tasker_admin? # => true
|
346
|
+
|
347
|
+
# This query succeeds for admin
|
348
|
+
query {
|
349
|
+
tasks {
|
350
|
+
taskId
|
351
|
+
workflowSteps { workflowStepId }
|
352
|
+
}
|
353
|
+
}
|
354
|
+
# ✅ 200 OK - Admin has access to all operations
|
355
|
+
|
356
|
+
# Regular user with limited permissions
|
357
|
+
user.has_tasker_permission?('tasker.task:index') # => true
|
358
|
+
user.has_tasker_permission?('tasker.workflow_step:index') # => false
|
359
|
+
|
360
|
+
# Same query for regular user
|
361
|
+
query {
|
362
|
+
tasks {
|
363
|
+
taskId
|
364
|
+
workflowSteps { workflowStepId }
|
365
|
+
}
|
366
|
+
}
|
367
|
+
# ❌ 403 Forbidden - User lacks tasker.workflow_step:index permission
|
368
|
+
|
369
|
+
# User can make this simpler query
|
370
|
+
query {
|
371
|
+
tasks {
|
372
|
+
taskId
|
373
|
+
status
|
374
|
+
}
|
375
|
+
}
|
376
|
+
# ✅ 200 OK - User has tasker.task:index permission
|
377
|
+
```
|
378
|
+
|
379
|
+
### GraphQL with Context
|
380
|
+
|
381
|
+
GraphQL authorization includes context information for advanced logic:
|
382
|
+
|
383
|
+
```ruby
|
384
|
+
class YourAuthorizationCoordinator < Tasker::Authorization::BaseCoordinator
|
385
|
+
protected
|
386
|
+
|
387
|
+
def authorized?(resource, action, context = {})
|
388
|
+
# Context includes:
|
389
|
+
# - controller: GraphQL controller instance
|
390
|
+
# - query_string: Original GraphQL query
|
391
|
+
# - operation_name: Named operation (if provided)
|
392
|
+
# - variables: Query variables
|
393
|
+
|
394
|
+
case resource
|
395
|
+
when Tasker::Authorization::ResourceConstants::RESOURCES::TASK
|
396
|
+
authorize_task_with_context(action, context)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
private
|
401
|
+
|
402
|
+
def authorize_task_with_context(action, context)
|
403
|
+
case action
|
404
|
+
when :show
|
405
|
+
# Allow users to view their own tasks
|
406
|
+
task_id = extract_task_id_from_context(context)
|
407
|
+
user.tasker_admin? || user_owns_task?(task_id)
|
408
|
+
when :index
|
409
|
+
# Regular index permission
|
410
|
+
user.has_tasker_permission?("tasker.task:index")
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
```
|
415
|
+
|
416
|
+
## Authenticator Generator
|
417
|
+
|
418
|
+
Tasker provides a Rails generator to quickly create authenticator templates for common authentication systems:
|
419
|
+
|
420
|
+
### Basic Usage
|
421
|
+
|
422
|
+
```bash
|
423
|
+
# Generate a JWT authenticator
|
424
|
+
rails generate tasker:authenticator CompanyJWT --type=jwt
|
425
|
+
|
426
|
+
# Generate a Devise authenticator
|
427
|
+
rails generate tasker:authenticator AdminAuth --type=devise --user-class=Admin
|
428
|
+
|
429
|
+
# Generate an API token authenticator
|
430
|
+
rails generate tasker:authenticator ApiAuth --type=api_token
|
431
|
+
|
432
|
+
# Generate an OmniAuth authenticator
|
433
|
+
rails generate tasker:authenticator SocialAuth --type=omniauth
|
434
|
+
|
435
|
+
# Generate a custom authenticator template
|
436
|
+
rails generate tasker:authenticator CustomAuth --type=custom
|
437
|
+
```
|
438
|
+
|
439
|
+
### Generator Options
|
440
|
+
|
441
|
+
- `--type`: Authenticator type (jwt, devise, api_token, omniauth, custom)
|
442
|
+
- `--user-class`: User model class name (default: User)
|
443
|
+
- `--directory`: Output directory (default: app/tasker/authenticators)
|
444
|
+
- `--with-spec/--no-with-spec`: Generate spec file (default: true)
|
445
|
+
|
446
|
+
### What the Generator Creates
|
447
|
+
|
448
|
+
The generator creates:
|
449
|
+
|
450
|
+
1. **Authenticator Class**: Complete implementation with security best practices
|
451
|
+
2. **Spec File**: Comprehensive test suite with example test cases
|
452
|
+
3. **Configuration Example**: Ready-to-use configuration for your initializer
|
453
|
+
4. **Usage Instructions**: Step-by-step setup guide with next steps
|
454
|
+
|
455
|
+
### Example: JWT Authenticator Generation
|
456
|
+
|
457
|
+
```bash
|
458
|
+
rails generate tasker:authenticator CompanyJWT --type=jwt --user-class=User
|
459
|
+
```
|
460
|
+
|
461
|
+
**Creates:**
|
462
|
+
- `app/tasker/authenticators/company_jwt_authenticator.rb` - Full JWT implementation
|
463
|
+
- `spec/tasker/authenticators/company_jwt_authenticator_spec.rb` - Test suite
|
464
|
+
- Configuration example and setup instructions
|
465
|
+
|
466
|
+
**Generated features:**
|
467
|
+
- JWT signature verification with configurable algorithms
|
468
|
+
- Bearer token and raw token support
|
469
|
+
- Comprehensive validation with security checks
|
470
|
+
- Test token generation helper for testing
|
471
|
+
- Memoized user lookup for performance
|
472
|
+
|
473
|
+
## Configuration Options
|
474
|
+
|
475
|
+
### Authentication Configuration Block
|
476
|
+
|
477
|
+
```ruby
|
478
|
+
Tasker.configuration do |config|
|
479
|
+
config.auth do |auth|
|
480
|
+
# Authentication settings
|
481
|
+
auth.authentication_enabled = true | false # Enable/disable authentication
|
482
|
+
auth.authenticator_class = 'String' # Your authenticator class name
|
483
|
+
|
484
|
+
# Authorization settings
|
485
|
+
auth.authorization_enabled = true | false # Enable/disable authorization
|
486
|
+
auth.authorization_coordinator_class = 'String' # Your authorization coordinator class
|
487
|
+
auth.user_class = 'String' # Your user model class name
|
488
|
+
end
|
489
|
+
end
|
490
|
+
```
|
491
|
+
|
492
|
+
### Configuration Validation
|
493
|
+
|
494
|
+
Tasker validates configuration at startup and provides helpful error messages:
|
495
|
+
|
496
|
+
```ruby
|
497
|
+
# Missing authenticator class when authentication is enabled
|
498
|
+
auth.authentication_enabled = true
|
499
|
+
auth.authenticator_class = nil
|
500
|
+
# => ConfigurationError: "Authentication is enabled but no authenticator_class is specified"
|
501
|
+
|
502
|
+
# Invalid authenticator class
|
503
|
+
auth.authenticator_class = 'NonExistentClass'
|
504
|
+
# => ConfigurationError: "Authenticator configuration errors: User class 'NonExistentClass' not found"
|
505
|
+
```
|
506
|
+
|
507
|
+
## Authentication Configuration
|
508
|
+
|
509
|
+
### No Authentication (Default)
|
510
|
+
|
511
|
+
By default, authentication is disabled. All users are considered "authenticated" with no user object.
|
512
|
+
|
513
|
+
```ruby
|
514
|
+
config.auth do |auth|
|
515
|
+
auth.authentication_enabled = false # Default
|
516
|
+
end
|
517
|
+
```
|
518
|
+
|
519
|
+
**Use Cases:**
|
520
|
+
- Development environments
|
521
|
+
- Internal tools without user management
|
522
|
+
- Public APIs
|
523
|
+
- Testing scenarios
|
524
|
+
|
525
|
+
### Custom Authentication
|
526
|
+
|
527
|
+
Host application provides a custom authenticator class that implements the authentication interface.
|
528
|
+
|
529
|
+
```ruby
|
530
|
+
config.auth do |auth|
|
531
|
+
auth.authentication_enabled = true
|
532
|
+
auth.authenticator_class = 'DeviseAuthenticator'
|
533
|
+
# Your authenticator can accept any configuration options in its initialize method
|
534
|
+
end
|
535
|
+
```
|
536
|
+
|
537
|
+
**Use Cases:**
|
538
|
+
- Devise integration
|
539
|
+
- JWT authentication
|
540
|
+
- OmniAuth integration
|
541
|
+
- Custom authentication systems
|
542
|
+
- Multi-tenant authentication
|
543
|
+
|
544
|
+
## Authorization System
|
545
|
+
|
546
|
+
Tasker's authorization system provides enterprise-grade security through a resource-based permission model. The system uses **resource constants** and **authorization coordinators** to ensure consistent, maintainable authorization logic.
|
547
|
+
|
548
|
+
### Resource Registry
|
549
|
+
|
550
|
+
All authorization revolves around the central resource registry:
|
551
|
+
|
552
|
+
```ruby
|
553
|
+
# Available Resources and Actions
|
554
|
+
Resources:
|
555
|
+
- tasker.task (index, show, create, update, destroy, retry, cancel)
|
556
|
+
- tasker.workflow_step (index, show, update, destroy, retry, cancel)
|
557
|
+
- tasker.task_diagram (index, show)
|
558
|
+
|
559
|
+
# Permission Examples:
|
560
|
+
'tasker.task:index' # List all tasks
|
561
|
+
'tasker.task:create' # Create new tasks
|
562
|
+
'tasker.workflow_step:show' # View individual workflow steps
|
563
|
+
'tasker.task_diagram:index' # List task diagrams
|
564
|
+
```
|
565
|
+
|
566
|
+
### Authorization Coordinator Interface
|
567
|
+
|
568
|
+
Authorization coordinators must implement the `BaseCoordinator` interface:
|
569
|
+
|
570
|
+
```ruby
|
571
|
+
class YourAuthorizationCoordinator < Tasker::Authorization::BaseCoordinator
|
572
|
+
protected
|
573
|
+
|
574
|
+
# Required: Implement authorization logic
|
575
|
+
def authorized?(resource, action, context = {})
|
576
|
+
# Return true if user is authorized for resource:action
|
577
|
+
# Context provides additional information like controller, params
|
578
|
+
end
|
579
|
+
end
|
580
|
+
```
|
581
|
+
|
582
|
+
### Controller Integration
|
583
|
+
|
584
|
+
Authorization is **automatically applied** to all Tasker controllers:
|
585
|
+
|
586
|
+
```ruby
|
587
|
+
# app/controllers/tasker/application_controller.rb
|
588
|
+
module Tasker
|
589
|
+
class ApplicationController < ActionController::Base
|
590
|
+
include Tasker::Concerns::Authenticatable # Authentication
|
591
|
+
include Tasker::Concerns::ControllerAuthorizable # Authorization
|
592
|
+
|
593
|
+
# All controllers automatically inherit both authentication and authorization
|
594
|
+
end
|
595
|
+
end
|
596
|
+
```
|
597
|
+
|
598
|
+
### Permission Checking Flow
|
599
|
+
|
600
|
+
1. **Request arrives** at Tasker controller (REST or GraphQL)
|
601
|
+
2. **Authentication** runs first (if enabled)
|
602
|
+
3. **Authorization** extracts `resource:action` from route/operation
|
603
|
+
4. **Coordinator** checks if current user has permission
|
604
|
+
5. **Access granted** (200 OK) or **denied** (403 Forbidden)
|
605
|
+
|
606
|
+
### Available Resources and Actions
|
607
|
+
|
608
|
+
#### Tasks (`tasker.task`)
|
609
|
+
- `index` - List all tasks
|
610
|
+
- `show` - View specific task
|
611
|
+
- `create` - Create new task
|
612
|
+
- `update` - Modify existing task
|
613
|
+
- `destroy` - Delete task
|
614
|
+
- `retry` - Retry failed task
|
615
|
+
- `cancel` - Cancel running task
|
616
|
+
|
617
|
+
#### Workflow Steps (`tasker.workflow_step`)
|
618
|
+
- `index` - List workflow steps
|
619
|
+
- `show` - View specific step
|
620
|
+
- `update` - Modify step
|
621
|
+
- `destroy` - Delete step
|
622
|
+
- `retry` - Retry failed step
|
623
|
+
- `cancel` - Cancel running step
|
624
|
+
|
625
|
+
#### Task Diagrams (`tasker.task_diagram`)
|
626
|
+
- `index` - List task diagrams
|
627
|
+
- `show` - View specific diagram
|
628
|
+
|
629
|
+
## Building Authorization Coordinators
|
630
|
+
|
631
|
+
Authorization coordinators provide the business logic for permission checking. Here's how to build effective coordinators:
|
632
|
+
|
633
|
+
### Basic Coordinator Structure
|
634
|
+
|
635
|
+
```ruby
|
636
|
+
class CompanyAuthorizationCoordinator < Tasker::Authorization::BaseCoordinator
|
637
|
+
protected
|
638
|
+
|
639
|
+
def authorized?(resource, action, context = {})
|
640
|
+
# Route to resource-specific methods
|
641
|
+
case resource
|
642
|
+
when Tasker::Authorization::ResourceConstants::RESOURCES::TASK
|
643
|
+
authorize_task(action, context)
|
644
|
+
when Tasker::Authorization::ResourceConstants::RESOURCES::WORKFLOW_STEP
|
645
|
+
authorize_workflow_step(action, context)
|
646
|
+
when Tasker::Authorization::ResourceConstants::RESOURCES::TASK_DIAGRAM
|
647
|
+
authorize_task_diagram(action, context)
|
648
|
+
else
|
649
|
+
false
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
private
|
654
|
+
|
655
|
+
def authorize_task(action, context)
|
656
|
+
case action
|
657
|
+
when :index, :show
|
658
|
+
# Read operations - regular users allowed
|
659
|
+
user.has_tasker_permission?("tasker.task:#{action}")
|
660
|
+
when :create, :update, :destroy, :retry, :cancel
|
661
|
+
# Write operations - admin or explicit permission
|
662
|
+
user.tasker_admin? || user.has_tasker_permission?("tasker.task:#{action}")
|
663
|
+
else
|
664
|
+
false
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def authorize_workflow_step(action, context)
|
669
|
+
case action
|
670
|
+
when :index, :show
|
671
|
+
# Read operations
|
672
|
+
user.has_tasker_permission?("tasker.workflow_step:#{action}")
|
673
|
+
when :update, :destroy, :retry, :cancel
|
674
|
+
# Write operations - admin only for steps
|
675
|
+
user.tasker_admin?
|
676
|
+
else
|
677
|
+
false
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
def authorize_task_diagram(action, context)
|
682
|
+
case action
|
683
|
+
when :index, :show
|
684
|
+
# Diagram viewing
|
685
|
+
user.has_tasker_permission?("tasker.task_diagram:#{action}")
|
686
|
+
else
|
687
|
+
false
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|
691
|
+
```
|
692
|
+
|
693
|
+
### Advanced Authorization Patterns
|
694
|
+
|
695
|
+
#### Role-Based Authorization
|
696
|
+
```ruby
|
697
|
+
def authorize_task(action, context)
|
698
|
+
case user.primary_role
|
699
|
+
when 'admin'
|
700
|
+
true # Admins can do everything
|
701
|
+
when 'manager'
|
702
|
+
[:index, :show, :create, :update, :retry].include?(action)
|
703
|
+
when 'operator'
|
704
|
+
[:index, :show, :retry].include?(action)
|
705
|
+
when 'viewer'
|
706
|
+
[:index, :show].include?(action)
|
707
|
+
else
|
708
|
+
false
|
709
|
+
end
|
710
|
+
end
|
711
|
+
```
|
712
|
+
|
713
|
+
#### Context-Based Authorization
|
714
|
+
```ruby
|
715
|
+
def authorize_task(action, context)
|
716
|
+
case action
|
717
|
+
when :show, :update, :cancel
|
718
|
+
task_id = context[:resource_id]
|
719
|
+
|
720
|
+
# Users can manage their own tasks
|
721
|
+
return true if user_owns_task?(task_id)
|
722
|
+
|
723
|
+
# Managers can manage team tasks
|
724
|
+
return true if user.manager? && team_owns_task?(task_id)
|
725
|
+
|
726
|
+
# Admins can manage all tasks
|
727
|
+
user.tasker_admin?
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
private
|
732
|
+
|
733
|
+
def user_owns_task?(task_id)
|
734
|
+
task = Tasker::Task.find_by(task_id: task_id)
|
735
|
+
return false unless task
|
736
|
+
|
737
|
+
task.context['created_by_user_id'] == user.id.to_s
|
738
|
+
end
|
739
|
+
|
740
|
+
def team_owns_task?(task_id)
|
741
|
+
task = Tasker::Task.find_by(task_id: task_id)
|
742
|
+
return false unless task
|
743
|
+
|
744
|
+
team_id = task.context['team_id']
|
745
|
+
user.managed_teams.include?(team_id)
|
746
|
+
end
|
747
|
+
```
|
748
|
+
|
749
|
+
#### Time-Based Authorization
|
750
|
+
```ruby
|
751
|
+
def authorize_task(action, context)
|
752
|
+
# Prevent modifications during maintenance windows
|
753
|
+
if maintenance_window_active?
|
754
|
+
return [:index, :show].include?(action)
|
755
|
+
end
|
756
|
+
|
757
|
+
# Business hours restrictions for certain actions
|
758
|
+
if [:destroy, :cancel].include?(action) && !business_hours?
|
759
|
+
return user.tasker_admin?
|
760
|
+
end
|
761
|
+
|
762
|
+
# Standard authorization
|
763
|
+
user.has_tasker_permission?("tasker.task:#{action}")
|
764
|
+
end
|
765
|
+
```
|
766
|
+
|
767
|
+
## Building Custom Authenticators
|
768
|
+
|
769
|
+
### Authentication Interface
|
770
|
+
|
771
|
+
All custom authenticators must implement the `Tasker::Authentication::Interface`:
|
772
|
+
|
773
|
+
```ruby
|
774
|
+
class YourCustomAuthenticator
|
775
|
+
include Tasker::Authentication::Interface
|
776
|
+
|
777
|
+
def initialize(options = {})
|
778
|
+
# Initialize with configuration options
|
779
|
+
@options = options
|
780
|
+
end
|
781
|
+
|
782
|
+
# Required: Authenticate the request, raise exception if fails
|
783
|
+
def authenticate!(controller)
|
784
|
+
# Implementation depends on your authentication system
|
785
|
+
# Raise Tasker::Authentication::AuthenticationError if authentication fails
|
786
|
+
end
|
787
|
+
|
788
|
+
# Required: Get the current authenticated user
|
789
|
+
def current_user(controller)
|
790
|
+
# Return user object or nil
|
791
|
+
end
|
792
|
+
|
793
|
+
# Optional: Check if user is authenticated (uses current_user by default)
|
794
|
+
def authenticated?(controller)
|
795
|
+
current_user(controller).present?
|
796
|
+
end
|
797
|
+
|
798
|
+
# Optional: Configuration validation
|
799
|
+
def validate_configuration(options = {})
|
800
|
+
errors = []
|
801
|
+
# Add validation logic, return array of error messages
|
802
|
+
errors
|
803
|
+
end
|
804
|
+
|
805
|
+
private
|
806
|
+
|
807
|
+
attr_reader :options
|
808
|
+
end
|
809
|
+
```
|
810
|
+
|
811
|
+
### Required Methods
|
812
|
+
|
813
|
+
#### `authenticate!(controller)`
|
814
|
+
|
815
|
+
**Purpose**: Authenticate the current request and raise an exception if authentication fails.
|
816
|
+
|
817
|
+
**Parameters**:
|
818
|
+
- `controller`: The Rails controller instance
|
819
|
+
|
820
|
+
**Behavior**:
|
821
|
+
- Must raise `Tasker::Authentication::AuthenticationError` if authentication fails
|
822
|
+
- Should return truthy value on success
|
823
|
+
- Called automatically by the `Authenticatable` concern
|
824
|
+
|
825
|
+
#### `current_user(controller)`
|
826
|
+
|
827
|
+
**Purpose**: Return the currently authenticated user object.
|
828
|
+
|
829
|
+
**Parameters**:
|
830
|
+
- `controller`: The Rails controller instance
|
831
|
+
|
832
|
+
**Returns**:
|
833
|
+
- User object if authenticated
|
834
|
+
- `nil` if not authenticated
|
835
|
+
|
836
|
+
**Notes**:
|
837
|
+
- Should be memoized for performance
|
838
|
+
- User object can be any class that represents your authenticated user
|
839
|
+
|
840
|
+
### Optional Methods
|
841
|
+
|
842
|
+
#### `authenticated?(controller)`
|
843
|
+
|
844
|
+
**Purpose**: Check if the current request is authenticated.
|
845
|
+
|
846
|
+
**Default Implementation**: Returns `current_user(controller).present?`
|
847
|
+
|
848
|
+
**Override**: When you need custom authentication logic beyond user presence.
|
849
|
+
|
850
|
+
#### `validate_configuration(options = {})`
|
851
|
+
|
852
|
+
**Purpose**: Validate authenticator-specific configuration options.
|
853
|
+
|
854
|
+
**Parameters**:
|
855
|
+
- `options`: Hash of configuration options
|
856
|
+
|
857
|
+
**Returns**: Array of error message strings (empty array if valid)
|
858
|
+
|
859
|
+
**Best Practices**:
|
860
|
+
- Check for required configuration options
|
861
|
+
- Validate external dependencies (gems, classes)
|
862
|
+
- Verify security settings (key lengths, algorithm choices)
|
863
|
+
|
864
|
+
## JWT Authentication Example
|
865
|
+
|
866
|
+
Our `ExampleJWTAuthenticator` demonstrates a production-ready JWT implementation:
|
867
|
+
|
868
|
+
### Basic Configuration
|
869
|
+
|
870
|
+
```ruby
|
871
|
+
# config/initializers/tasker.rb
|
872
|
+
Tasker.configuration do |config|
|
873
|
+
config.auth do |auth|
|
874
|
+
auth.authentication_enabled = true
|
875
|
+
auth.authenticator_class = 'ExampleJWTAuthenticator'
|
876
|
+
auth.user_class = 'User'
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
# Your JWT authenticator can receive configuration in its initialize method:
|
881
|
+
class ExampleJWTAuthenticator
|
882
|
+
def initialize(options = {})
|
883
|
+
@secret = Rails.application.credentials.jwt_secret
|
884
|
+
@algorithm = 'HS256'
|
885
|
+
@header_name = 'Authorization'
|
886
|
+
@user_class = 'User'
|
887
|
+
end
|
888
|
+
# ... rest of implementation
|
889
|
+
end
|
890
|
+
```
|
891
|
+
|
892
|
+
### Environment-Specific Configuration
|
893
|
+
|
894
|
+
```ruby
|
895
|
+
# config/initializers/tasker.rb
|
896
|
+
Tasker.configuration do |config|
|
897
|
+
config.auth do |auth|
|
898
|
+
auth.authentication_enabled = true
|
899
|
+
auth.authenticator_class = 'ExampleJWTAuthenticator'
|
900
|
+
auth.user_class = 'User'
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
# Your JWT authenticator handles environment-specific configuration internally:
|
905
|
+
class ExampleJWTAuthenticator
|
906
|
+
def initialize(options = {})
|
907
|
+
@secret = Rails.env.production? ?
|
908
|
+
Rails.application.credentials.jwt_secret :
|
909
|
+
'development-secret-key-32-chars-min'
|
910
|
+
@algorithm = Rails.env.production? ? 'HS512' : 'HS256'
|
911
|
+
@user_class = 'User'
|
912
|
+
end
|
913
|
+
# ... rest of implementation
|
914
|
+
end
|
915
|
+
```
|
916
|
+
|
917
|
+
### Implementation Highlights
|
918
|
+
|
919
|
+
The `ExampleJWTAuthenticator` includes:
|
920
|
+
|
921
|
+
```ruby
|
922
|
+
class ExampleJWTAuthenticator
|
923
|
+
include Tasker::Authentication::Interface
|
924
|
+
|
925
|
+
def initialize(options = {})
|
926
|
+
@secret = options[:secret]
|
927
|
+
@algorithm = options[:algorithm] || 'HS256'
|
928
|
+
@header_name = options[:header_name] || 'Authorization'
|
929
|
+
@user_class = options[:user_class] || 'User'
|
930
|
+
end
|
931
|
+
|
932
|
+
def authenticate!(controller)
|
933
|
+
user = current_user(controller)
|
934
|
+
unless user
|
935
|
+
raise Tasker::Authentication::AuthenticationError,
|
936
|
+
"Invalid or missing JWT token"
|
937
|
+
end
|
938
|
+
true
|
939
|
+
end
|
940
|
+
|
941
|
+
def current_user(controller)
|
942
|
+
return @current_user if defined?(@current_user)
|
943
|
+
|
944
|
+
@current_user = begin
|
945
|
+
token = extract_token(controller.request)
|
946
|
+
return nil unless token
|
947
|
+
|
948
|
+
payload = decode_token(token)
|
949
|
+
return nil unless payload
|
950
|
+
|
951
|
+
find_user(payload)
|
952
|
+
rescue JWT::DecodeError, StandardError
|
953
|
+
nil
|
954
|
+
end
|
955
|
+
end
|
956
|
+
|
957
|
+
def validate_configuration(options = {})
|
958
|
+
errors = []
|
959
|
+
|
960
|
+
# Validate JWT secret
|
961
|
+
secret = options[:secret]
|
962
|
+
if secret.blank?
|
963
|
+
errors << "JWT secret is required"
|
964
|
+
elsif secret.length < 32
|
965
|
+
errors << "JWT secret should be at least 32 characters for security"
|
966
|
+
end
|
967
|
+
|
968
|
+
# Validate algorithm
|
969
|
+
algorithm = options[:algorithm] || 'HS256'
|
970
|
+
allowed_algorithms = %w[HS256 HS384 HS512 RS256 RS384 RS512 ES256 ES384 ES512]
|
971
|
+
unless allowed_algorithms.include?(algorithm)
|
972
|
+
errors << "JWT algorithm must be one of: #{allowed_algorithms.join(', ')}"
|
973
|
+
end
|
974
|
+
|
975
|
+
errors
|
976
|
+
end
|
977
|
+
|
978
|
+
private
|
979
|
+
|
980
|
+
def extract_token(request)
|
981
|
+
header = request.headers[@header_name]
|
982
|
+
return nil unless header.present?
|
983
|
+
|
984
|
+
# Support both "Bearer <token>" and raw token formats
|
985
|
+
header.start_with?('Bearer ') ? header.sub(/^Bearer /, '') : header
|
986
|
+
end
|
987
|
+
|
988
|
+
def decode_token(token)
|
989
|
+
payload, _header = JWT.decode(
|
990
|
+
token,
|
991
|
+
@secret,
|
992
|
+
true, # verify signature
|
993
|
+
{
|
994
|
+
algorithm: @algorithm,
|
995
|
+
verify_expiration: true,
|
996
|
+
verify_iat: true
|
997
|
+
}
|
998
|
+
)
|
999
|
+
payload
|
1000
|
+
rescue JWT::ExpiredSignature, JWT::InvalidIatError
|
1001
|
+
nil
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
def find_user(payload)
|
1005
|
+
user_id = payload['user_id'] || payload['sub']
|
1006
|
+
return nil unless user_id
|
1007
|
+
|
1008
|
+
user_model = @user_class.constantize
|
1009
|
+
user_model.find_by(id: user_id)
|
1010
|
+
rescue ActiveRecord::RecordNotFound, NoMethodError
|
1011
|
+
nil
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
```
|
1015
|
+
|
1016
|
+
### Security Features
|
1017
|
+
|
1018
|
+
- **Signature Verification**: Validates JWT signatures to prevent tampering
|
1019
|
+
- **Expiration Checking**: Automatically rejects expired tokens
|
1020
|
+
- **Algorithm Validation**: Ensures only approved algorithms are used
|
1021
|
+
- **Secret Length Validation**: Enforces minimum security standards
|
1022
|
+
- **Error Handling**: Graceful handling of malformed or invalid tokens
|
1023
|
+
|
1024
|
+
## Integration with Controllers
|
1025
|
+
|
1026
|
+
### Automatic Integration
|
1027
|
+
|
1028
|
+
Controllers inherit **both authentication and authorization** automatically:
|
1029
|
+
|
1030
|
+
```ruby
|
1031
|
+
# app/controllers/tasker/application_controller.rb
|
1032
|
+
module Tasker
|
1033
|
+
class ApplicationController < ActionController::Base
|
1034
|
+
include Tasker::Concerns::Authenticatable # Authentication
|
1035
|
+
include Tasker::Concerns::ControllerAuthorizable # Authorization
|
1036
|
+
|
1037
|
+
# Both authentication and authorization happen automatically
|
1038
|
+
# before_action :authenticate_tasker_user!
|
1039
|
+
# before_action :authorize_tasker_action!
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
```
|
1043
|
+
|
1044
|
+
### Automatic Authorization
|
1045
|
+
|
1046
|
+
All Tasker controllers automatically enforce authorization when enabled:
|
1047
|
+
|
1048
|
+
```ruby
|
1049
|
+
class Tasker::TasksController < ApplicationController
|
1050
|
+
# These actions are automatically protected:
|
1051
|
+
|
1052
|
+
def index
|
1053
|
+
# Requires 'tasker.task:index' permission
|
1054
|
+
# Authorization runs before this method
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
def show
|
1058
|
+
# Requires 'tasker.task:show' permission
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
def create
|
1062
|
+
# Requires 'tasker.task:create' permission
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
def update
|
1066
|
+
# Requires 'tasker.task:update' permission
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
def destroy
|
1070
|
+
# Requires 'tasker.task:destroy' permission
|
1071
|
+
end
|
1072
|
+
end
|
1073
|
+
```
|
1074
|
+
|
1075
|
+
### Available Helper Methods
|
1076
|
+
|
1077
|
+
In any Tasker controller, you have access to:
|
1078
|
+
|
1079
|
+
```ruby
|
1080
|
+
class Tasker::TasksController < ApplicationController
|
1081
|
+
def index
|
1082
|
+
# Authentication methods
|
1083
|
+
if tasker_user_authenticated?
|
1084
|
+
user = current_tasker_user # Get current user object
|
1085
|
+
|
1086
|
+
# Authorization methods
|
1087
|
+
coordinator = authorization_coordinator
|
1088
|
+
if coordinator.can?('tasker.task', :create)
|
1089
|
+
# User can create tasks
|
1090
|
+
end
|
1091
|
+
end
|
1092
|
+
end
|
1093
|
+
end
|
1094
|
+
```
|
1095
|
+
|
1096
|
+
### Controller Methods
|
1097
|
+
|
1098
|
+
**Authentication Methods:**
|
1099
|
+
- `current_tasker_user`: Returns the current user object (or nil)
|
1100
|
+
- `tasker_user_authenticated?`: Returns boolean authentication status
|
1101
|
+
- `authenticate_tasker_user!`: Manually trigger authentication (called automatically)
|
1102
|
+
|
1103
|
+
**Authorization Methods:**
|
1104
|
+
- `authorization_coordinator`: Returns the current authorization coordinator
|
1105
|
+
- `authorize_tasker_action!`: Manually trigger authorization (called automatically)
|
1106
|
+
- `skip_authorization?`: Check if authorization should be skipped
|
1107
|
+
|
1108
|
+
### REST API Authorization
|
1109
|
+
|
1110
|
+
REST endpoints map directly to resource:action permissions:
|
1111
|
+
|
1112
|
+
```ruby
|
1113
|
+
# HTTP Method + Route = Resource:Action Permission
|
1114
|
+
|
1115
|
+
GET /tasker/tasks → tasker.task:index
|
1116
|
+
GET /tasker/tasks/123 → tasker.task:show
|
1117
|
+
POST /tasker/tasks → tasker.task:create
|
1118
|
+
PATCH /tasker/tasks/123 → tasker.task:update
|
1119
|
+
DELETE /tasker/tasks/123 → tasker.task:destroy
|
1120
|
+
|
1121
|
+
GET /tasker/tasks/123/workflow_steps → tasker.workflow_step:index
|
1122
|
+
GET /tasker/workflow_steps/456 → tasker.workflow_step:show
|
1123
|
+
PATCH /tasker/workflow_steps/456 → tasker.workflow_step:update
|
1124
|
+
|
1125
|
+
GET /tasker/tasks/123/task_diagrams → tasker.task_diagram:index
|
1126
|
+
GET /tasker/task_diagrams/789 → tasker.task_diagram:show
|
1127
|
+
```
|
1128
|
+
|
1129
|
+
### GraphQL Integration
|
1130
|
+
|
1131
|
+
GraphQL endpoints inherit **both authentication and authorization** with operation-level granular security:
|
1132
|
+
|
1133
|
+
```ruby
|
1134
|
+
# app/controllers/tasker/graphql_controller.rb
|
1135
|
+
module Tasker
|
1136
|
+
class GraphqlController < ApplicationController
|
1137
|
+
# Inherits Authenticatable and ControllerAuthorizable
|
1138
|
+
# Skip standard controller authorization - we handle GraphQL operations manually
|
1139
|
+
skip_before_action :authorize_tasker_action!, if: :authorization_enabled?
|
1140
|
+
|
1141
|
+
def execute
|
1142
|
+
# Authentication runs automatically
|
1143
|
+
# GraphQL authorization runs per-operation
|
1144
|
+
|
1145
|
+
# Context includes authenticated user
|
1146
|
+
context = {
|
1147
|
+
current_user: current_tasker_user,
|
1148
|
+
authenticated: tasker_user_authenticated?
|
1149
|
+
}
|
1150
|
+
|
1151
|
+
# Operations are authorized individually
|
1152
|
+
result = Tasker::TaskerRailsSchema.execute(query, variables: variables, context: context)
|
1153
|
+
render(json: result)
|
1154
|
+
end
|
1155
|
+
end
|
1156
|
+
end
|
1157
|
+
```
|
1158
|
+
|
1159
|
+
### GraphQL Resolver Authorization
|
1160
|
+
|
1161
|
+
In GraphQL resolvers, you have access to authorization context:
|
1162
|
+
|
1163
|
+
```ruby
|
1164
|
+
# app/graphql/tasker/queries/tasks_query.rb
|
1165
|
+
module Tasker
|
1166
|
+
module Queries
|
1167
|
+
class TasksQuery < BaseQuery
|
1168
|
+
def resolve(**args)
|
1169
|
+
user = context[:current_user]
|
1170
|
+
|
1171
|
+
# Authorization was already checked before this resolver runs
|
1172
|
+
# The query 'tasks' required 'tasker.task:index' permission
|
1173
|
+
|
1174
|
+
# Your query logic here
|
1175
|
+
Tasker::Task.all
|
1176
|
+
end
|
1177
|
+
end
|
1178
|
+
end
|
1179
|
+
end
|
1180
|
+
```
|
1181
|
+
|
1182
|
+
### Manual Authorization
|
1183
|
+
|
1184
|
+
You can also perform manual authorization checks:
|
1185
|
+
|
1186
|
+
```ruby
|
1187
|
+
class CustomController < Tasker::ApplicationController
|
1188
|
+
def custom_action
|
1189
|
+
# Manual authorization check
|
1190
|
+
coordinator = authorization_coordinator
|
1191
|
+
|
1192
|
+
unless coordinator.can?('tasker.task', :create)
|
1193
|
+
render json: { error: 'Not authorized' }, status: :forbidden
|
1194
|
+
return
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
# Proceed with authorized logic
|
1198
|
+
end
|
1199
|
+
end
|
1200
|
+
```
|
1201
|
+
|
1202
|
+
## Error Handling
|
1203
|
+
|
1204
|
+
### Authentication Errors
|
1205
|
+
|
1206
|
+
The system provides standardized error handling:
|
1207
|
+
|
1208
|
+
```ruby
|
1209
|
+
# HTTP Status Codes
|
1210
|
+
401 Unauthorized # Authentication required or failed
|
1211
|
+
500 Internal Server Error # Configuration or interface errors
|
1212
|
+
```
|
1213
|
+
|
1214
|
+
### Error Types
|
1215
|
+
|
1216
|
+
```ruby
|
1217
|
+
# Authentication failed
|
1218
|
+
Tasker::Authentication::AuthenticationError
|
1219
|
+
# => 401 Unauthorized response
|
1220
|
+
|
1221
|
+
# Invalid authenticator configuration
|
1222
|
+
Tasker::Authentication::ConfigurationError
|
1223
|
+
# => 500 Internal Server Error response
|
1224
|
+
|
1225
|
+
# Authenticator doesn't implement required interface
|
1226
|
+
Tasker::Authentication::InterfaceError
|
1227
|
+
# => 500 Internal Server Error response
|
1228
|
+
```
|
1229
|
+
|
1230
|
+
### Custom Error Messages
|
1231
|
+
|
1232
|
+
Authenticators can provide meaningful error messages:
|
1233
|
+
|
1234
|
+
```ruby
|
1235
|
+
def authenticate!(controller)
|
1236
|
+
token = extract_token(controller.request)
|
1237
|
+
|
1238
|
+
unless token
|
1239
|
+
raise Tasker::Authentication::AuthenticationError,
|
1240
|
+
"Authorization header missing. Please provide a valid JWT token."
|
1241
|
+
end
|
1242
|
+
|
1243
|
+
unless valid_token?(token)
|
1244
|
+
raise Tasker::Authentication::AuthenticationError,
|
1245
|
+
"Invalid JWT token. Please check your credentials and try again."
|
1246
|
+
end
|
1247
|
+
end
|
1248
|
+
```
|
1249
|
+
|
1250
|
+
## Testing Authentication
|
1251
|
+
|
1252
|
+
### Test Authenticator
|
1253
|
+
|
1254
|
+
For testing, use the provided `TestAuthenticator`:
|
1255
|
+
|
1256
|
+
```ruby
|
1257
|
+
# spec/support/authentication_helper.rb
|
1258
|
+
RSpec.configure do |config|
|
1259
|
+
config.before(:each) do
|
1260
|
+
# Reset authentication state
|
1261
|
+
TestAuthenticator.reset!
|
1262
|
+
end
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
# In tests
|
1266
|
+
describe 'authenticated endpoint' do
|
1267
|
+
before do
|
1268
|
+
# Configure test authentication
|
1269
|
+
TestAuthenticator.set_authentication_result(true)
|
1270
|
+
TestAuthenticator.set_current_user(TestUser.new(id: 1, name: 'Test User'))
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
it 'allows access for authenticated users' do
|
1274
|
+
get '/tasker/tasks'
|
1275
|
+
expect(response).to have_http_status(:ok)
|
1276
|
+
end
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
describe 'unauthenticated access' do
|
1280
|
+
before do
|
1281
|
+
TestAuthenticator.set_authentication_result(false)
|
1282
|
+
TestAuthenticator.set_current_user(nil)
|
1283
|
+
end
|
1284
|
+
|
1285
|
+
it 'denies access for unauthenticated users' do
|
1286
|
+
get '/tasker/tasks'
|
1287
|
+
expect(response).to have_http_status(:unauthorized)
|
1288
|
+
end
|
1289
|
+
end
|
1290
|
+
```
|
1291
|
+
|
1292
|
+
### JWT Testing
|
1293
|
+
|
1294
|
+
For JWT authenticator testing:
|
1295
|
+
|
1296
|
+
```ruby
|
1297
|
+
# Generate test tokens
|
1298
|
+
test_secret = 'test-secret-key-32-characters-plus'
|
1299
|
+
user_token = ExampleJWTAuthenticator.generate_test_token(
|
1300
|
+
user_id: 1,
|
1301
|
+
secret: test_secret
|
1302
|
+
)
|
1303
|
+
|
1304
|
+
# Use in request specs
|
1305
|
+
headers = { 'Authorization' => "Bearer #{user_token}" }
|
1306
|
+
get '/tasker/tasks', headers: headers
|
1307
|
+
```
|
1308
|
+
|
1309
|
+
## Testing Authorization
|
1310
|
+
|
1311
|
+
### Authorization Test Setup
|
1312
|
+
|
1313
|
+
For authorization testing, use comprehensive integration tests:
|
1314
|
+
|
1315
|
+
```ruby
|
1316
|
+
# spec/support/shared_contexts/configuration_test_isolation.rb
|
1317
|
+
RSpec.shared_context 'configuration test isolation' do
|
1318
|
+
around(:each) do |example|
|
1319
|
+
original_config = Tasker.configuration
|
1320
|
+
example.run
|
1321
|
+
ensure
|
1322
|
+
# Reset to clean state
|
1323
|
+
Tasker.instance_variable_set(:@configuration, original_config)
|
1324
|
+
end
|
1325
|
+
end
|
1326
|
+
|
1327
|
+
# spec/requests/tasker/authorization_integration_spec.rb
|
1328
|
+
require 'rails_helper'
|
1329
|
+
|
1330
|
+
RSpec.describe 'Authorization Integration', type: :request do
|
1331
|
+
include_context 'configuration test isolation'
|
1332
|
+
|
1333
|
+
let(:admin_user) do
|
1334
|
+
TestUser.new(
|
1335
|
+
id: 1,
|
1336
|
+
permissions: [],
|
1337
|
+
roles: ['admin'],
|
1338
|
+
admin: true
|
1339
|
+
)
|
1340
|
+
end
|
1341
|
+
|
1342
|
+
let(:regular_user) do
|
1343
|
+
TestUser.new(
|
1344
|
+
id: 2,
|
1345
|
+
permissions: [
|
1346
|
+
'tasker.task:index',
|
1347
|
+
'tasker.task:show',
|
1348
|
+
'tasker.workflow_step:index',
|
1349
|
+
'tasker.task_diagram:index'
|
1350
|
+
],
|
1351
|
+
roles: ['user'],
|
1352
|
+
admin: false
|
1353
|
+
)
|
1354
|
+
end
|
1355
|
+
|
1356
|
+
before do
|
1357
|
+
configure_tasker_auth(
|
1358
|
+
strategy: :custom,
|
1359
|
+
options: { authenticator_class: 'TestAuthenticator' },
|
1360
|
+
enabled: true,
|
1361
|
+
coordinator_class: 'CustomAuthorizationCoordinator'
|
1362
|
+
)
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
describe 'with admin user' do
|
1366
|
+
before do
|
1367
|
+
TestAuthenticator.set_authentication_result(true)
|
1368
|
+
TestAuthenticator.set_current_user(admin_user)
|
1369
|
+
end
|
1370
|
+
|
1371
|
+
it 'allows full access to all resources' do
|
1372
|
+
get '/tasker/tasks'
|
1373
|
+
expect(response).to have_http_status(:ok)
|
1374
|
+
|
1375
|
+
post '/tasker/tasks', params: { task: valid_task_params }.to_json,
|
1376
|
+
headers: { 'Content-Type' => 'application/json' }
|
1377
|
+
expect(response).to have_http_status(:created)
|
1378
|
+
end
|
1379
|
+
end
|
1380
|
+
|
1381
|
+
describe 'with regular user' do
|
1382
|
+
before do
|
1383
|
+
TestAuthenticator.set_authentication_result(true)
|
1384
|
+
TestAuthenticator.set_current_user(regular_user)
|
1385
|
+
end
|
1386
|
+
|
1387
|
+
it 'allows access to permitted resources' do
|
1388
|
+
get '/tasker/tasks'
|
1389
|
+
expect(response).to have_http_status(:ok)
|
1390
|
+
end
|
1391
|
+
|
1392
|
+
it 'denies access to forbidden resources' do
|
1393
|
+
post '/tasker/tasks', params: { task: valid_task_params }.to_json,
|
1394
|
+
headers: { 'Content-Type' => 'application/json' }
|
1395
|
+
expect(response).to have_http_status(:forbidden)
|
1396
|
+
end
|
1397
|
+
end
|
1398
|
+
end
|
1399
|
+
```
|
1400
|
+
|
1401
|
+
### GraphQL Authorization Testing
|
1402
|
+
|
1403
|
+
```ruby
|
1404
|
+
describe 'GraphQL Authorization' do
|
1405
|
+
it 'allows authorized GraphQL operations' do
|
1406
|
+
TestAuthenticator.set_current_user(admin_user)
|
1407
|
+
|
1408
|
+
post '/tasker/graphql', params: {
|
1409
|
+
query: 'query { tasks { taskId status } }'
|
1410
|
+
}
|
1411
|
+
expect(response).to have_http_status(:ok)
|
1412
|
+
end
|
1413
|
+
|
1414
|
+
it 'blocks unauthorized GraphQL operations' do
|
1415
|
+
TestAuthenticator.set_current_user(regular_user)
|
1416
|
+
|
1417
|
+
post '/tasker/graphql', params: {
|
1418
|
+
query: 'mutation { createTask(input: { name: "test" }) { taskId } }'
|
1419
|
+
}
|
1420
|
+
expect(response).to have_http_status(:forbidden)
|
1421
|
+
end
|
1422
|
+
|
1423
|
+
it 'handles mixed operations correctly' do
|
1424
|
+
TestAuthenticator.set_current_user(regular_user)
|
1425
|
+
|
1426
|
+
# User has tasker.task:index but not tasker.workflow_step:index
|
1427
|
+
post '/tasker/graphql', params: {
|
1428
|
+
query: 'query { tasks { taskId workflowSteps { workflowStepId } } }'
|
1429
|
+
}
|
1430
|
+
expect(response).to have_http_status(:forbidden)
|
1431
|
+
end
|
1432
|
+
end
|
1433
|
+
```
|
1434
|
+
|
1435
|
+
### Custom Authorization Coordinator Testing
|
1436
|
+
|
1437
|
+
```ruby
|
1438
|
+
describe CustomAuthorizationCoordinator do
|
1439
|
+
let(:coordinator) { described_class.new(user) }
|
1440
|
+
|
1441
|
+
describe 'task authorization' do
|
1442
|
+
context 'with admin user' do
|
1443
|
+
let(:user) { admin_user }
|
1444
|
+
|
1445
|
+
it 'allows all task operations' do
|
1446
|
+
expect(coordinator.can?('tasker.task', :index)).to be true
|
1447
|
+
expect(coordinator.can?('tasker.task', :create)).to be true
|
1448
|
+
expect(coordinator.can?('tasker.task', :destroy)).to be true
|
1449
|
+
end
|
1450
|
+
end
|
1451
|
+
|
1452
|
+
context 'with regular user' do
|
1453
|
+
let(:user) { regular_user }
|
1454
|
+
|
1455
|
+
it 'allows read operations' do
|
1456
|
+
expect(coordinator.can?('tasker.task', :index)).to be true
|
1457
|
+
expect(coordinator.can?('tasker.task', :show)).to be true
|
1458
|
+
end
|
1459
|
+
|
1460
|
+
it 'denies write operations' do
|
1461
|
+
expect(coordinator.can?('tasker.task', :create)).to be false
|
1462
|
+
expect(coordinator.can?('tasker.task', :destroy)).to be false
|
1463
|
+
end
|
1464
|
+
end
|
1465
|
+
end
|
1466
|
+
end
|
1467
|
+
```
|
1468
|
+
|
1469
|
+
### State Isolation
|
1470
|
+
|
1471
|
+
Ensure tests don't leak configuration state:
|
1472
|
+
|
1473
|
+
```ruby
|
1474
|
+
# spec/rails_helper.rb (automatic cleanup)
|
1475
|
+
RSpec.configure do |config|
|
1476
|
+
config.after(:each) do
|
1477
|
+
# Automatic cleanup of authentication/authorization state
|
1478
|
+
if defined?(Tasker) && Tasker.respond_to?(:configuration)
|
1479
|
+
current_config = Tasker.configuration
|
1480
|
+
if current_config&.auth&.authorization_enabled == true
|
1481
|
+
needs_reset = true
|
1482
|
+
end
|
1483
|
+
|
1484
|
+
if current_config&.auth&.authentication_enabled == true
|
1485
|
+
authenticator_class = current_config.auth.authenticator_class
|
1486
|
+
needs_reset = true if authenticator_class&.include?('Test')
|
1487
|
+
end
|
1488
|
+
|
1489
|
+
if needs_reset
|
1490
|
+
Tasker.configuration do |config|
|
1491
|
+
config.auth.authentication_enabled = false
|
1492
|
+
config.auth.authorization_enabled = false
|
1493
|
+
config.auth.authenticator_class = nil
|
1494
|
+
config.auth.authorization_coordinator_class = nil
|
1495
|
+
end
|
1496
|
+
end
|
1497
|
+
end
|
1498
|
+
end
|
1499
|
+
end
|
1500
|
+
```
|
1501
|
+
|
1502
|
+
## Best Practices
|
1503
|
+
|
1504
|
+
### Security
|
1505
|
+
|
1506
|
+
#### Authentication Security
|
1507
|
+
1. **Use Strong Secrets**: Minimum 32 characters for JWT secrets
|
1508
|
+
2. **Choose Secure Algorithms**: Prefer HS256/HS512 for HMAC, RS256+ for RSA
|
1509
|
+
3. **Validate Configuration**: Implement `validate_configuration` for security checks
|
1510
|
+
4. **Handle Errors Gracefully**: Never expose sensitive information in error messages
|
1511
|
+
5. **Implement Token Expiration**: Always set reasonable expiration times
|
1512
|
+
|
1513
|
+
#### Authorization Security
|
1514
|
+
1. **Default Deny**: Always default to denying access unless explicitly granted
|
1515
|
+
2. **Resource-Specific Logic**: Implement granular permissions per resource type
|
1516
|
+
3. **Context-Aware Authorization**: Use context for ownership and relationship checks
|
1517
|
+
4. **Admin Override Pattern**: Allow admins to bypass specific restrictions safely
|
1518
|
+
5. **Audit Trails**: Log authorization decisions for security monitoring
|
1519
|
+
|
1520
|
+
```ruby
|
1521
|
+
# Good: Default deny with explicit grants
|
1522
|
+
def authorize_task(action, context)
|
1523
|
+
return false unless user.present? # Default deny
|
1524
|
+
|
1525
|
+
case action
|
1526
|
+
when :index, :show
|
1527
|
+
user.has_tasker_permission?("tasker.task:#{action}")
|
1528
|
+
when :create, :update, :destroy
|
1529
|
+
user.tasker_admin? || user.has_tasker_permission?("tasker.task:#{action}")
|
1530
|
+
else
|
1531
|
+
false # Explicit deny for unknown actions
|
1532
|
+
end
|
1533
|
+
end
|
1534
|
+
|
1535
|
+
# Bad: Default allow
|
1536
|
+
def authorize_task(action, context)
|
1537
|
+
return true if user.tasker_admin? # Too broad
|
1538
|
+
# ... other logic
|
1539
|
+
end
|
1540
|
+
```
|
1541
|
+
|
1542
|
+
### Performance
|
1543
|
+
|
1544
|
+
#### Authentication Performance
|
1545
|
+
1. **Memoize User Lookups**: Cache user objects within request scope
|
1546
|
+
2. **Efficient Database Queries**: Use `find_by` instead of exceptions for user lookup
|
1547
|
+
3. **Minimal Token Validation**: Only decode/validate tokens once per request
|
1548
|
+
|
1549
|
+
#### Authorization Performance
|
1550
|
+
1. **Cache Permission Checks**: Memoize authorization decisions within request scope
|
1551
|
+
2. **Efficient Permission Storage**: Use optimized data structures for permission lookups
|
1552
|
+
3. **Minimal Database Hits**: Load user permissions once per request
|
1553
|
+
4. **Smart GraphQL Batching**: Group permission checks for related operations
|
1554
|
+
|
1555
|
+
```ruby
|
1556
|
+
# Good: Memoized authorization
|
1557
|
+
class YourAuthorizationCoordinator < Tasker::Authorization::BaseCoordinator
|
1558
|
+
def can?(resource, action, context = {})
|
1559
|
+
cache_key = "#{resource}:#{action}"
|
1560
|
+
@authorization_cache ||= {}
|
1561
|
+
@authorization_cache[cache_key] ||= super
|
1562
|
+
end
|
1563
|
+
end
|
1564
|
+
|
1565
|
+
# Good: Efficient permission checking
|
1566
|
+
def user_permissions
|
1567
|
+
@user_permissions ||= user.permissions.to_set
|
1568
|
+
end
|
1569
|
+
|
1570
|
+
def has_permission?(permission)
|
1571
|
+
user_permissions.include?(permission)
|
1572
|
+
end
|
1573
|
+
```
|
1574
|
+
|
1575
|
+
### Development
|
1576
|
+
|
1577
|
+
#### General Development
|
1578
|
+
1. **Use Different Configs for Environments**: Separate dev/test/production settings
|
1579
|
+
2. **Provide Clear Error Messages**: Help developers debug configuration issues
|
1580
|
+
3. **Document Your Authenticator**: Include usage examples and configuration options
|
1581
|
+
4. **Test Edge Cases**: Expired tokens, malformed headers, missing users
|
1582
|
+
|
1583
|
+
#### Authorization Development
|
1584
|
+
1. **Resource Constants**: Always use `ResourceConstants` instead of hardcoded strings
|
1585
|
+
2. **Comprehensive Testing**: Test both positive and negative authorization scenarios
|
1586
|
+
3. **Clear Coordinator Logic**: Separate resource authorization into dedicated methods
|
1587
|
+
4. **Context Documentation**: Document what context information your coordinator uses
|
1588
|
+
|
1589
|
+
```ruby
|
1590
|
+
# Good: Using constants
|
1591
|
+
when Tasker::Authorization::ResourceConstants::RESOURCES::TASK
|
1592
|
+
authorize_task_action(action, context)
|
1593
|
+
|
1594
|
+
# Bad: Hardcoded strings
|
1595
|
+
when 'tasker.task'
|
1596
|
+
authorize_task_action(action, context)
|
1597
|
+
```
|
1598
|
+
|
1599
|
+
### Code Organization
|
1600
|
+
|
1601
|
+
```ruby
|
1602
|
+
# Recommended file structure
|
1603
|
+
app/
|
1604
|
+
tasker/
|
1605
|
+
authenticators/
|
1606
|
+
company_jwt_authenticator.rb
|
1607
|
+
company_devise_authenticator.rb
|
1608
|
+
authorization/
|
1609
|
+
company_authorization_coordinator.rb
|
1610
|
+
models/
|
1611
|
+
user.rb # Include Tasker::Concerns::Authorizable
|
1612
|
+
config/
|
1613
|
+
initializers/
|
1614
|
+
tasker.rb # Authentication & authorization configuration
|
1615
|
+
|
1616
|
+
# Authorization coordinator organization
|
1617
|
+
class CompanyAuthorizationCoordinator < Tasker::Authorization::BaseCoordinator
|
1618
|
+
protected
|
1619
|
+
|
1620
|
+
def authorized?(resource, action, context = {})
|
1621
|
+
case resource
|
1622
|
+
when ResourceConstants::RESOURCES::TASK
|
1623
|
+
authorize_task(action, context)
|
1624
|
+
when ResourceConstants::RESOURCES::WORKFLOW_STEP
|
1625
|
+
authorize_workflow_step(action, context)
|
1626
|
+
when ResourceConstants::RESOURCES::TASK_DIAGRAM
|
1627
|
+
authorize_task_diagram(action, context)
|
1628
|
+
else
|
1629
|
+
false
|
1630
|
+
end
|
1631
|
+
end
|
1632
|
+
|
1633
|
+
private
|
1634
|
+
|
1635
|
+
# Separate methods for each resource type
|
1636
|
+
def authorize_task(action, context)
|
1637
|
+
# Task-specific authorization logic
|
1638
|
+
end
|
1639
|
+
|
1640
|
+
def authorize_workflow_step(action, context)
|
1641
|
+
# Workflow step authorization logic
|
1642
|
+
end
|
1643
|
+
|
1644
|
+
def authorize_task_diagram(action, context)
|
1645
|
+
# Diagram authorization logic
|
1646
|
+
end
|
1647
|
+
end
|
1648
|
+
```
|
1649
|
+
|
1650
|
+
### Production Considerations
|
1651
|
+
|
1652
|
+
#### Monitoring & Observability
|
1653
|
+
1. **Log Authorization Failures**: Track unauthorized access attempts
|
1654
|
+
2. **Monitor Performance**: Track authorization overhead
|
1655
|
+
3. **Alert on Anomalies**: Detect unusual permission patterns
|
1656
|
+
4. **Audit Admin Actions**: Log all administrative overrides
|
1657
|
+
|
1658
|
+
#### Scaling Authorization
|
1659
|
+
1. **Permission Caching**: Cache user permissions with appropriate TTL
|
1660
|
+
2. **Database Optimization**: Index permission lookup columns
|
1661
|
+
3. **Background Processing**: Refresh permissions asynchronously when possible
|
1662
|
+
4. **Circuit Breakers**: Graceful degradation when authorization services are unavailable
|
1663
|
+
|
1664
|
+
### Example Authenticator Template
|
1665
|
+
|
1666
|
+
```ruby
|
1667
|
+
class YourAuthenticator
|
1668
|
+
include Tasker::Authentication::Interface
|
1669
|
+
|
1670
|
+
def initialize(options = {})
|
1671
|
+
@options = options
|
1672
|
+
# Initialize your authenticator
|
1673
|
+
end
|
1674
|
+
|
1675
|
+
def authenticate!(controller)
|
1676
|
+
# Your authentication logic
|
1677
|
+
user = current_user(controller)
|
1678
|
+
unless user
|
1679
|
+
raise Tasker::Authentication::AuthenticationError, "Authentication failed"
|
1680
|
+
end
|
1681
|
+
true
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
def current_user(controller)
|
1685
|
+
return @current_user if defined?(@current_user)
|
1686
|
+
|
1687
|
+
@current_user = begin
|
1688
|
+
# Your user lookup logic
|
1689
|
+
rescue StandardError
|
1690
|
+
nil
|
1691
|
+
end
|
1692
|
+
end
|
1693
|
+
|
1694
|
+
def validate_configuration(options = {})
|
1695
|
+
errors = []
|
1696
|
+
# Add your validation logic
|
1697
|
+
errors
|
1698
|
+
end
|
1699
|
+
|
1700
|
+
private
|
1701
|
+
|
1702
|
+
attr_reader :options
|
1703
|
+
|
1704
|
+
# Your private helper methods
|
1705
|
+
end
|
1706
|
+
```
|
1707
|
+
|
1708
|
+
## Common Integration Patterns
|
1709
|
+
|
1710
|
+
### Devise Integration
|
1711
|
+
|
1712
|
+
```ruby
|
1713
|
+
class DeviseAuthenticator
|
1714
|
+
include Tasker::Authentication::Interface
|
1715
|
+
|
1716
|
+
def initialize(options = {})
|
1717
|
+
@scope = options[:scope] || :user
|
1718
|
+
end
|
1719
|
+
|
1720
|
+
def authenticate!(controller)
|
1721
|
+
controller.send("authenticate_#{@scope}!")
|
1722
|
+
end
|
1723
|
+
|
1724
|
+
def current_user(controller)
|
1725
|
+
controller.send("current_#{@scope}")
|
1726
|
+
end
|
1727
|
+
|
1728
|
+
def validate_configuration(options = {})
|
1729
|
+
errors = []
|
1730
|
+
unless defined?(Devise)
|
1731
|
+
errors << "Devise gem is required for DeviseAuthenticator"
|
1732
|
+
end
|
1733
|
+
errors
|
1734
|
+
end
|
1735
|
+
|
1736
|
+
private
|
1737
|
+
|
1738
|
+
attr_reader :scope
|
1739
|
+
end
|
1740
|
+
```
|
1741
|
+
|
1742
|
+
### API Token Authentication
|
1743
|
+
|
1744
|
+
```ruby
|
1745
|
+
class ApiTokenAuthenticator
|
1746
|
+
include Tasker::Authentication::Interface
|
1747
|
+
|
1748
|
+
def initialize(options = {})
|
1749
|
+
@header_name = options[:header_name] || 'X-API-Token'
|
1750
|
+
@user_class = options[:user_class] || 'User'
|
1751
|
+
end
|
1752
|
+
|
1753
|
+
def authenticate!(controller)
|
1754
|
+
user = current_user(controller)
|
1755
|
+
unless user
|
1756
|
+
raise Tasker::Authentication::AuthenticationError, "Invalid API token"
|
1757
|
+
end
|
1758
|
+
true
|
1759
|
+
end
|
1760
|
+
|
1761
|
+
def current_user(controller)
|
1762
|
+
return @current_user if defined?(@current_user)
|
1763
|
+
|
1764
|
+
@current_user = begin
|
1765
|
+
token = controller.request.headers[@header_name]
|
1766
|
+
return nil unless token
|
1767
|
+
|
1768
|
+
user_class.constantize.find_by(api_token: token)
|
1769
|
+
rescue ActiveRecord::RecordNotFound
|
1770
|
+
nil
|
1771
|
+
end
|
1772
|
+
end
|
1773
|
+
|
1774
|
+
private
|
1775
|
+
|
1776
|
+
attr_reader :header_name, :user_class
|
1777
|
+
end
|
1778
|
+
```
|
1779
|
+
|
1780
|
+
This authentication system provides the flexibility to integrate with any authentication solution while maintaining security, performance, and developer experience. The dependency injection pattern ensures that Tasker remains authentication-agnostic while providing a robust foundation for secure applications.
|