command_tower 0.4.0 → 0.11.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 +4 -4
- data/README.md +106 -29
- data/app/auth/command_tower/auth/auth_context.rb +42 -0
- data/app/auth/command_tower/auth/password_recovery_session_context.rb +11 -0
- data/app/auth/command_tower/auth/signup_session_context.rb +11 -0
- data/app/controllers/command_tower/admin/application_controller.rb +27 -0
- data/app/controllers/command_tower/admin/audit/events_controller.rb +61 -0
- data/app/controllers/command_tower/admin/messaging/announcements_controller.rb +27 -0
- data/app/controllers/command_tower/admin/users/identities_controller.rb +77 -0
- data/app/controllers/command_tower/admin/users/impersonation_sessions_controller.rb +33 -0
- data/app/controllers/command_tower/admin/users/roles_controller.rb +40 -0
- data/app/controllers/command_tower/admin/users_controller.rb +46 -0
- data/app/controllers/command_tower/admin/workspace_controller.rb +15 -0
- data/app/controllers/command_tower/application_controller.rb +97 -15
- data/app/controllers/command_tower/auth/email/availability_controller.rb +35 -0
- data/app/controllers/command_tower/auth/email_verification/send_controller.rb +24 -0
- data/app/controllers/command_tower/auth/email_verification/verify_controller.rb +41 -0
- data/app/controllers/command_tower/auth/identity_policy_controller.rb +14 -0
- data/app/controllers/command_tower/auth/impersonation_session_controller.rb +27 -0
- data/app/controllers/command_tower/auth/logout_controller.rb +16 -0
- data/app/controllers/command_tower/auth/password_recovery_session/create_controller.rb +17 -0
- data/app/controllers/command_tower/auth/password_reset/reset_controller.rb +35 -0
- data/app/controllers/command_tower/auth/password_reset/send_controller.rb +40 -0
- data/app/controllers/command_tower/auth/password_reset/validate_controller.rb +35 -0
- data/app/controllers/command_tower/auth/plain_text/login_controller.rb +22 -0
- data/app/controllers/command_tower/auth/principal_capabilities_controller.rb +19 -0
- data/app/controllers/command_tower/auth/register_controller.rb +33 -0
- data/app/controllers/command_tower/auth/session_controller.rb +21 -0
- data/app/controllers/command_tower/auth/signup_session/create_controller.rb +17 -0
- data/app/controllers/command_tower/auth/username/availability_controller.rb +35 -0
- data/app/controllers/command_tower/me/audit_events_controller.rb +59 -0
- data/app/controllers/command_tower/me/inbox_controller.rb +85 -0
- data/app/controllers/command_tower/me/name_controller.rb +37 -0
- data/app/controllers/command_tower/me/password_controller.rb +37 -0
- data/app/controllers/command_tower/me/phone_controller.rb +44 -0
- data/app/controllers/command_tower/me/phone_verification/verifications_controller.rb +23 -0
- data/app/controllers/command_tower/me/phone_verification/verify_controller.rb +34 -0
- data/app/controllers/command_tower/me/preferences_controller.rb +46 -0
- data/app/controllers/command_tower/me/pushover/verifications_controller.rb +23 -0
- data/app/controllers/command_tower/me/pushover_controller.rb +66 -0
- data/app/controllers/command_tower/me_controller.rb +19 -0
- data/app/controllers/command_tower/profile_controller.rb +19 -0
- data/app/controllers/concerns/command_tower/api/application_response_renderer.rb +87 -0
- data/app/controllers/concerns/command_tower/api/deserializer_failure_details.rb +22 -0
- data/app/controllers/concerns/command_tower/auth/authentication_boundary.rb +35 -0
- data/app/controllers/concerns/command_tower/auth/authorization_boundary.rb +26 -0
- data/app/controllers/concerns/command_tower/auth/invalid_credentials_deserializer_renderer.rb +23 -0
- data/app/controllers/concerns/command_tower/auth/password_recovery_session_boundary.rb +29 -0
- data/app/controllers/concerns/command_tower/auth/signup_session_boundary.rb +29 -0
- data/app/controllers/concerns/command_tower/execution/http_boundary.rb +65 -0
- data/app/deserializers/command_tower/deserializers/admin/messaging/create_announcement_deserializer.rb +106 -0
- data/app/deserializers/command_tower/deserializers/admin/scope_parameter.rb +49 -0
- data/app/deserializers/command_tower/deserializers/admin/users.rb +218 -0
- data/app/deserializers/command_tower/deserializers/application_deserializer.rb +148 -0
- data/app/deserializers/command_tower/deserializers/audit/events.rb +224 -0
- data/app/deserializers/command_tower/deserializers/auth/email_availability_deserializer.rb +19 -0
- data/app/deserializers/command_tower/deserializers/auth/email_verification/verify_deserializer.rb +21 -0
- data/app/deserializers/command_tower/deserializers/auth/password_reset/reset_deserializer.rb +31 -0
- data/app/deserializers/command_tower/deserializers/auth/password_reset/send_deserializer.rb +21 -0
- data/app/deserializers/command_tower/deserializers/auth/password_reset/validate_deserializer.rb +23 -0
- data/app/deserializers/command_tower/deserializers/auth/plain_text/login_deserializer.rb +24 -0
- data/app/deserializers/command_tower/deserializers/auth/register_deserializer.rb +42 -0
- data/app/deserializers/command_tower/deserializers/auth/username_availability_deserializer.rb +19 -0
- data/app/deserializers/command_tower/deserializers/clients/errors.rb +45 -0
- data/app/deserializers/command_tower/deserializers/clients/missing.rb +10 -0
- data/app/deserializers/command_tower/deserializers/clients/payload.rb +21 -0
- data/app/deserializers/command_tower/deserializers/clients/result.rb +122 -0
- data/app/deserializers/command_tower/deserializers/clients/types.rb +219 -0
- data/app/deserializers/command_tower/deserializers/coercion_result.rb +35 -0
- data/app/deserializers/command_tower/deserializers/failure.rb +14 -0
- data/app/deserializers/command_tower/deserializers/me/change_password_deserializer.rb +41 -0
- data/app/deserializers/command_tower/deserializers/me/phone_verification/verify_deserializer.rb +29 -0
- data/app/deserializers/command_tower/deserializers/me/pushover/credentials_deserializer.rb +31 -0
- data/app/deserializers/command_tower/deserializers/me/update_name_deserializer.rb +22 -0
- data/app/deserializers/command_tower/deserializers/me/update_phone_deserializer.rb +22 -0
- data/app/deserializers/command_tower/deserializers/messaging/inbox.rb +77 -0
- data/app/deserializers/command_tower/deserializers/messaging/preferences/show_deserializer.rb +17 -0
- data/app/deserializers/command_tower/deserializers/messaging/preferences/update_deserializer.rb +97 -0
- data/app/errors/command_tower/errors/account/phone_already_verified_error.rb +17 -0
- data/app/errors/command_tower/errors/account/phone_missing_error.rb +17 -0
- data/app/errors/command_tower/errors/account/phone_verification_code_invalid_error.rb +17 -0
- data/app/errors/command_tower/errors/account/phone_verification_expired_error.rb +17 -0
- data/app/errors/command_tower/errors/account/phone_verification_send_failed_error.rb +25 -0
- data/app/errors/command_tower/errors/account/phone_verification_stale_error.rb +17 -0
- data/app/errors/command_tower/errors/account/phone_verification_throttled_error.rb +24 -0
- data/app/errors/command_tower/errors/account/pushover_already_configured_error.rb +21 -0
- data/app/errors/command_tower/errors/account/pushover_capability_unavailable_error.rb +21 -0
- data/app/errors/command_tower/errors/account/pushover_not_configured_error.rb +21 -0
- data/app/errors/command_tower/errors/account/pushover_provider_unavailable_error.rb +21 -0
- data/app/errors/command_tower/errors/account/pushover_verification_failed_error.rb +27 -0
- data/app/errors/command_tower/errors/account/sms_capability_unavailable_error.rb +21 -0
- data/app/errors/command_tower/errors/application_error.rb +27 -0
- data/app/errors/command_tower/errors/auth/admin_unavailable_during_impersonation_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/csrf_mismatch_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/csrf_missing_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/default_membership_assignment_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/email_already_registered_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/email_verification_required_error.rb +21 -0
- data/app/errors/command_tower/errors/auth/impersonation_session_expired_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/impersonation_session_missing_error.rb +21 -0
- data/app/errors/command_tower/errors/auth/invalid_credentials_error.rb +21 -0
- data/app/errors/command_tower/errors/auth/nested_impersonation_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/password_recovery_ip_rate_limit_error.rb +25 -0
- data/app/errors/command_tower/errors/auth/password_recovery_session_expired_error.rb +21 -0
- data/app/errors/command_tower/errors/auth/password_recovery_session_invalid_error.rb +21 -0
- data/app/errors/command_tower/errors/auth/password_recovery_session_missing_error.rb +21 -0
- data/app/errors/command_tower/errors/auth/password_recovery_session_rate_limit_error.rb +25 -0
- data/app/errors/command_tower/errors/auth/password_reset_invalid_token_error.rb +21 -0
- data/app/errors/command_tower/errors/auth/password_reset_unavailable_error.rb +21 -0
- data/app/errors/command_tower/errors/auth/self_impersonation_error.rb +21 -0
- data/app/errors/command_tower/errors/auth/signup_ip_rate_limit_error.rb +25 -0
- data/app/errors/command_tower/errors/auth/signup_session_expired_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/signup_session_invalid_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/signup_session_missing_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/signup_session_rate_limit_error.rb +25 -0
- data/app/errors/command_tower/errors/auth/verification_code_invalid_error.rb +17 -0
- data/app/errors/command_tower/errors/auth/verification_send_failed_error.rb +25 -0
- data/app/errors/command_tower/errors/continuation_exhausted_error.rb +25 -0
- data/app/errors/command_tower/errors/forbidden_error.rb +19 -0
- data/app/errors/command_tower/errors/internal_error.rb +19 -0
- data/app/errors/command_tower/errors/messaging/accept_rejected_error.rb +21 -0
- data/app/errors/command_tower/errors/messaging/idempotency_conflict_error.rb +21 -0
- data/app/errors/command_tower/errors/messaging/recipient_unresolved_error.rb +21 -0
- data/app/errors/command_tower/errors/not_found_error.rb +19 -0
- data/app/errors/command_tower/errors/unauthorized_error.rb +19 -0
- data/app/errors/command_tower/errors/validation_error.rb +15 -0
- data/app/jobs/command_tower/application_job.rb +7 -0
- data/app/jobs/command_tower/execution/job_boundary.rb +19 -0
- data/app/jobs/command_tower/messaging/channel_delivery_execution_job.rb +18 -0
- data/app/jobs/command_tower/messaging/communications/produce_recipient_job.rb +24 -0
- data/app/jobs/command_tower/messaging/execution_recovery_job.rb +13 -0
- data/app/jobs/command_tower/messaging/handoff_job.rb +15 -0
- data/app/jobs/command_tower/messaging/handoff_recovery_job.rb +13 -0
- data/app/mailers/command_tower/application_mailer.rb +11 -1
- data/app/mailers/command_tower/email_verification_mailer.rb +5 -3
- data/app/mailers/command_tower/messaging/channel_mailer.rb +24 -0
- data/app/mailers/command_tower/password_reset_mailer.rb +18 -0
- data/app/models/command_tower/audit/event.rb +36 -0
- data/app/models/command_tower/impersonation/session.rb +51 -0
- data/app/models/command_tower/messaging/channel_delivery.rb +45 -0
- data/app/models/command_tower/messaging/communication.rb +60 -0
- data/app/models/command_tower/messaging/delivery_attempt.rb +28 -0
- data/app/models/command_tower/messaging/destination_plan.rb +17 -0
- data/app/models/command_tower/messaging/endpoint.rb +98 -0
- data/app/models/command_tower/messaging/endpoint_pushover_credential.rb +18 -0
- data/app/models/command_tower/messaging/inbox_item.rb +52 -0
- data/app/models/command_tower/messaging/notification_preference.rb +21 -0
- data/app/models/user.rb +4 -1
- data/app/serializers/command_tower/serializers/admin/messaging/announcement_response_serializer.rb +41 -0
- data/app/serializers/command_tower/serializers/admin/users.rb +69 -0
- data/app/serializers/command_tower/serializers/admin/workspace/manifest_serializer.rb +58 -0
- data/app/serializers/command_tower/serializers/application/envelope_serializer.rb +25 -0
- data/app/serializers/command_tower/serializers/application/error_entry_serializer.rb +18 -0
- data/app/serializers/command_tower/serializers/application_serializer.rb +26 -0
- data/app/serializers/command_tower/serializers/audit/events/filter_options_serializer.rb +44 -0
- data/app/serializers/command_tower/serializers/audit/events.rb +42 -0
- data/app/serializers/command_tower/serializers/auth/email_availability_serializer.rb +17 -0
- data/app/serializers/command_tower/serializers/auth/email_verification/message_response_serializer.rb +15 -0
- data/app/serializers/command_tower/serializers/auth/identity_policy_serializer.rb +36 -0
- data/app/serializers/command_tower/serializers/auth/login_response_serializer.rb +17 -0
- data/app/serializers/command_tower/serializers/auth/logout_response_serializer.rb +13 -0
- data/app/serializers/command_tower/serializers/auth/password_recovery_session_response_serializer.rb +16 -0
- data/app/serializers/command_tower/serializers/auth/password_reset/message_response_serializer.rb +15 -0
- data/app/serializers/command_tower/serializers/auth/password_reset/validate_response_serializer.rb +22 -0
- data/app/serializers/command_tower/serializers/auth/principal_capabilities_serializer.rb +15 -0
- data/app/serializers/command_tower/serializers/auth/register_response_serializer.rb +16 -0
- data/app/serializers/command_tower/serializers/auth/session_response_serializer.rb +40 -0
- data/app/serializers/command_tower/serializers/auth/signup_session_response_serializer.rb +16 -0
- data/app/serializers/command_tower/serializers/auth/user_serializer.rb +21 -0
- data/app/serializers/command_tower/serializers/auth/username_availability_serializer.rb +17 -0
- data/app/serializers/command_tower/serializers/impersonation/session_serializer.rb +19 -0
- data/app/serializers/command_tower/serializers/me/account_serializer.rb +45 -0
- data/app/serializers/command_tower/serializers/me/change_password_response_serializer.rb +13 -0
- data/app/serializers/command_tower/serializers/me/pushover_serializer.rb +59 -0
- data/app/serializers/command_tower/serializers/messaging/inbox.rb +45 -0
- data/app/serializers/command_tower/serializers/messaging/preferences/catalog_serializer.rb +17 -0
- data/app/serializers/command_tower/serializers/messaging/preferences/category_serializer.rb +23 -0
- data/app/serializers/command_tower/serializers/messaging/preferences/notification_serializer.rb +30 -0
- data/app/serializers/command_tower/serializers/profile/profile_serializer.rb +13 -0
- data/app/services/command_tower/README.md +24 -14
- data/app/services/command_tower/auth/request_context.rb +21 -0
- data/app/services/command_tower/authorize/validate.rb +58 -19
- data/app/services/command_tower/clients/client_base.rb +183 -0
- data/app/services/command_tower/clients/client_result.rb +45 -0
- data/app/services/command_tower/clients/const_resolution.rb +32 -0
- data/app/services/command_tower/clients/decoded_response.rb +16 -0
- data/app/services/command_tower/clients/endpoint_base.rb +242 -0
- data/app/services/command_tower/clients/endpoint_input.rb +18 -0
- data/app/services/command_tower/clients/errors/authentication_error.rb +26 -0
- data/app/services/command_tower/clients/errors/configuration_error.rb +10 -0
- data/app/services/command_tower/clients/errors/deserialization_error.rb +26 -0
- data/app/services/command_tower/clients/errors/discovery_error.rb +10 -0
- data/app/services/command_tower/clients/errors/upstream_error.rb +26 -0
- data/app/services/command_tower/clients/invocation.rb +61 -0
- data/app/services/command_tower/clients/namespace_proxy.rb +75 -0
- data/app/services/command_tower/clients/scoped_client.rb +88 -0
- data/app/services/command_tower/clients/serialized_request.rb +17 -0
- data/app/services/command_tower/clients/transport/error.rb +18 -0
- data/app/services/command_tower/clients/transport/faraday_adapter.rb +129 -0
- data/app/services/command_tower/clients/transport/faraday_config.rb +45 -0
- data/app/services/command_tower/clients/transport/request.rb +50 -0
- data/app/services/command_tower/clients/transport/response.rb +22 -0
- data/app/services/command_tower/clients/url.rb +56 -0
- data/app/services/command_tower/identity/phone/normalizer.rb +41 -0
- data/app/services/command_tower/identity/phone_verification/sms_configuration.rb +58 -0
- data/app/services/command_tower/identity/phone_verification/sms_transport/adapters/fake_adapter.rb +35 -0
- data/app/services/command_tower/identity/phone_verification/sms_transport/adapters/log_adapter.rb +34 -0
- data/app/services/command_tower/identity/phone_verification/sms_transport/adapters/twilio_adapter.rb +60 -0
- data/app/services/command_tower/identity/phone_verification/sms_transport.rb +42 -0
- data/app/services/command_tower/jwt/authenticate_user.rb +67 -34
- data/app/services/command_tower/jwt/authentication_outcome.rb +37 -0
- data/app/services/command_tower/jwt/decode.rb +27 -8
- data/app/services/command_tower/jwt/encode.rb +4 -6
- data/app/services/command_tower/jwt/login_create.rb +10 -8
- data/app/services/command_tower/login_strategy/plain_text/email_verification/required.rb +11 -9
- data/app/services/command_tower/messaging/accept/channel_delivery_result.rb +13 -0
- data/app/services/command_tower/messaging/accept/coordinator.rb +166 -0
- data/app/services/command_tower/messaging/accept/error.rb +9 -0
- data/app/services/command_tower/messaging/accept/excluded_destination.rb +24 -0
- data/app/services/command_tower/messaging/accept/handoff_scheduler.rb +40 -0
- data/app/services/command_tower/messaging/accept/idempotency_conflict_error.rb +9 -0
- data/app/services/command_tower/messaging/accept/illegal_override_error.rb +9 -0
- data/app/services/command_tower/messaging/accept/impossible_mandatory_plan_error.rb +9 -0
- data/app/services/command_tower/messaging/accept/invalid_preference_error.rb +9 -0
- data/app/services/command_tower/messaging/accept/invariant_error.rb +9 -0
- data/app/services/command_tower/messaging/accept/operation_logger.rb +97 -0
- data/app/services/command_tower/messaging/accept/persistence_error.rb +9 -0
- data/app/services/command_tower/messaging/accept/persister.rb +90 -0
- data/app/services/command_tower/messaging/accept/request_normalizer.rb +100 -0
- data/app/services/command_tower/messaging/accept/result.rb +99 -0
- data/app/services/command_tower/messaging/accept/unknown_type_error.rb +9 -0
- data/app/services/command_tower/messaging/accept/validation_error.rb +9 -0
- data/app/services/command_tower/messaging/channel_detectors.rb +52 -0
- data/app/services/command_tower/messaging/channels/definition.rb +24 -0
- data/app/services/command_tower/messaging/channels/error.rb +9 -0
- data/app/services/command_tower/messaging/channels/unknown_channel_error.rb +9 -0
- data/app/services/command_tower/messaging/channels.rb +85 -0
- data/app/services/command_tower/messaging/contract/communications.rb +29 -0
- data/app/services/command_tower/messaging/contract/error.rb +9 -0
- data/app/services/command_tower/messaging/contract/internal/finder.rb +43 -0
- data/app/services/command_tower/messaging/contract/invariant_error.rb +9 -0
- data/app/services/command_tower/messaging/contract/mappers/channel_delivery_mapper.rb +53 -0
- data/app/services/command_tower/messaging/contract/mappers/communication_mapper.rb +61 -0
- data/app/services/command_tower/messaging/contract/mappers/delivery_attempt_mapper.rb +27 -0
- data/app/services/command_tower/messaging/contract/mappers/destination_plan_mapper.rb +17 -0
- data/app/services/command_tower/messaging/contract/mappers/inbox_item_mapper.rb +17 -0
- data/app/services/command_tower/messaging/contract/not_found_error.rb +9 -0
- data/app/services/command_tower/messaging/contract/observability/correlation.rb +19 -0
- data/app/services/command_tower/messaging/contract/observability/operation_logger.rb +101 -0
- data/app/services/command_tower/messaging/contract/observability/publisher.rb +44 -0
- data/app/services/command_tower/messaging/contract/requests/find_communication.rb +21 -0
- data/app/services/command_tower/messaging/contract/results/channel_delivery_result.rb +25 -0
- data/app/services/command_tower/messaging/contract/results/communication_result.rb +22 -0
- data/app/services/command_tower/messaging/contract/results/delivery_attempt_result.rb +21 -0
- data/app/services/command_tower/messaging/contract/results/destination_plan_result.rb +11 -0
- data/app/services/command_tower/messaging/contract/results/inbox_item_result.rb +11 -0
- data/app/services/command_tower/messaging/contract/validation_error.rb +9 -0
- data/app/services/command_tower/messaging/delivery_retry_policy.rb +30 -0
- data/app/services/command_tower/messaging/endpoints/begin_verification.rb +13 -0
- data/app/services/command_tower/messaging/endpoints/channel_gate.rb +39 -0
- data/app/services/command_tower/messaging/endpoints/conflict_error.rb +9 -0
- data/app/services/command_tower/messaging/endpoints/create.rb +181 -0
- data/app/services/command_tower/messaging/endpoints/error.rb +9 -0
- data/app/services/command_tower/messaging/endpoints/fingerprinter.rb +50 -0
- data/app/services/command_tower/messaging/endpoints/invalid_transition_error.rb +9 -0
- data/app/services/command_tower/messaging/endpoints/lifecycle.rb +37 -0
- data/app/services/command_tower/messaging/endpoints/mark_invalid.rb +32 -0
- data/app/services/command_tower/messaging/endpoints/mark_verification_failed.rb +13 -0
- data/app/services/command_tower/messaging/endpoints/mark_verified.rb +13 -0
- data/app/services/command_tower/messaging/endpoints/not_found_error.rb +9 -0
- data/app/services/command_tower/messaging/endpoints/persistence.rb +91 -0
- data/app/services/command_tower/messaging/endpoints/pushover/verify.rb +101 -0
- data/app/services/command_tower/messaging/endpoints/query.rb +28 -0
- data/app/services/command_tower/messaging/endpoints/replace.rb +181 -0
- data/app/services/command_tower/messaging/endpoints/reset_verification.rb +13 -0
- data/app/services/command_tower/messaging/endpoints/revoke.rb +40 -0
- data/app/services/command_tower/messaging/endpoints/safe_view.rb +50 -0
- data/app/services/command_tower/messaging/endpoints/secret_box.rb +86 -0
- data/app/services/command_tower/messaging/endpoints/secret_reader.rb +72 -0
- data/app/services/command_tower/messaging/endpoints/validation_error.rb +9 -0
- data/app/services/command_tower/messaging/endpoints/validators/push.rb +24 -0
- data/app/services/command_tower/messaging/endpoints/validators/pushover.rb +68 -0
- data/app/services/command_tower/messaging/endpoints/validators.rb +36 -0
- data/app/services/command_tower/messaging/endpoints/verification.rb +39 -0
- data/app/services/command_tower/messaging/endpoints/verification_failed_error.rb +18 -0
- data/app/services/command_tower/messaging/endpoints/verification_transition.rb +20 -0
- data/app/services/command_tower/messaging/endpoints.rb +55 -0
- data/app/services/command_tower/messaging/execution/adapter_request.rb +45 -0
- data/app/services/command_tower/messaging/execution/adapter_result.rb +81 -0
- data/app/services/command_tower/messaging/execution/adapters/email/adapter.rb +78 -0
- data/app/services/command_tower/messaging/execution/adapters/email/configuration.rb +55 -0
- data/app/services/command_tower/messaging/execution/adapters/fake_adapter.rb +34 -0
- data/app/services/command_tower/messaging/execution/adapters/pushover/adapter.rb +107 -0
- data/app/services/command_tower/messaging/execution/adapters/pushover/configuration.rb +30 -0
- data/app/services/command_tower/messaging/execution/adapters/sms/adapter.rb +90 -0
- data/app/services/command_tower/messaging/execution/adapters/sms/configuration.rb +58 -0
- data/app/services/command_tower/messaging/execution/adapters/sms/log_adapter.rb +34 -0
- data/app/services/command_tower/messaging/execution/adapters/sms/twilio_http_client.rb +70 -0
- data/app/services/command_tower/messaging/execution/adapters/unconfigured_adapter.rb +20 -0
- data/app/services/command_tower/messaging/execution/attempt_ledger.rb +146 -0
- data/app/services/command_tower/messaging/execution/claim.rb +35 -0
- data/app/services/command_tower/messaging/execution/invalid_adapter_contract_error.rb +9 -0
- data/app/services/command_tower/messaging/execution/operation_logger.rb +237 -0
- data/app/services/command_tower/messaging/execution/readiness_terminal_mapping.rb +37 -0
- data/app/services/command_tower/messaging/execution/recovery.rb +78 -0
- data/app/services/command_tower/messaging/execution/resolve_recipient_address.rb +58 -0
- data/app/services/command_tower/messaging/execution/revalidate_readiness.rb +73 -0
- data/app/services/command_tower/messaging/execution/scheduler.rb +38 -0
- data/app/services/command_tower/messaging/execution/select_adapter.rb +63 -0
- data/app/services/command_tower/messaging/handoff/advance_communication.rb +39 -0
- data/app/services/command_tower/messaging/handoff/operation_logger.rb +78 -0
- data/app/services/command_tower/messaging/handoff/recovery.rb +26 -0
- data/app/services/command_tower/messaging/inbox/bulk_result.rb +19 -0
- data/app/services/command_tower/messaging/inbox/error.rb +9 -0
- data/app/services/command_tower/messaging/inbox/invalid_bulk_items_error.rb +19 -0
- data/app/services/command_tower/messaging/inbox/invariant_error.rb +9 -0
- data/app/services/command_tower/messaging/inbox/item_result.rb +64 -0
- data/app/services/command_tower/messaging/inbox/list_result.rb +18 -0
- data/app/services/command_tower/messaging/inbox/mutator.rb +252 -0
- data/app/services/command_tower/messaging/inbox/not_found_error.rb +9 -0
- data/app/services/command_tower/messaging/inbox/operation_logger.rb +37 -0
- data/app/services/command_tower/messaging/inbox/reader.rb +117 -0
- data/app/services/command_tower/messaging/inbox/scope.rb +24 -0
- data/app/services/command_tower/messaging/inbox/unread_count_result.rb +16 -0
- data/app/services/command_tower/messaging/inbox/validation_error.rb +9 -0
- data/app/services/command_tower/messaging/inbox.rb +61 -0
- data/app/services/command_tower/messaging/notification_types/catalog_category.rb +19 -0
- data/app/services/command_tower/messaging/notification_types/declaration.rb +96 -0
- data/app/services/command_tower/messaging/notification_types/declaration_validator.rb +152 -0
- data/app/services/command_tower/messaging/notification_types/duplicate_type_error.rb +9 -0
- data/app/services/command_tower/messaging/notification_types/error.rb +9 -0
- data/app/services/command_tower/messaging/notification_types/invalid_declaration_error.rb +9 -0
- data/app/services/command_tower/messaging/notification_types/not_found_error.rb +9 -0
- data/app/services/command_tower/messaging/notification_types/registry.rb +103 -0
- data/app/services/command_tower/messaging/notification_types/sealed_registry_error.rb +9 -0
- data/app/services/command_tower/messaging/notification_types.rb +37 -0
- data/app/services/command_tower/messaging/planner/destination_plan.rb +41 -0
- data/app/services/command_tower/messaging/planner/error.rb +9 -0
- data/app/services/command_tower/messaging/planner/excluded_destination.rb +28 -0
- data/app/services/command_tower/messaging/planner/illegal_override_error.rb +9 -0
- data/app/services/command_tower/messaging/planner/impossible_mandatory_plan_error.rb +9 -0
- data/app/services/command_tower/messaging/planner/invalid_evaluation_error.rb +9 -0
- data/app/services/command_tower/messaging/planner/message_overrides.rb +33 -0
- data/app/services/command_tower/messaging/planner/operation_logger.rb +28 -0
- data/app/services/command_tower/messaging/planner/planner.rb +261 -0
- data/app/services/command_tower/messaging/planner/unknown_type_error.rb +9 -0
- data/app/services/command_tower/messaging/planner.rb +25 -0
- data/app/services/command_tower/messaging/preferences/catalog.rb +96 -0
- data/app/services/command_tower/messaging/preferences/catalog_category_result.rb +19 -0
- data/app/services/command_tower/messaging/preferences/catalog_notification_result.rb +55 -0
- data/app/services/command_tower/messaging/preferences/catalog_result.rb +13 -0
- data/app/services/command_tower/messaging/preferences/error.rb +9 -0
- data/app/services/command_tower/messaging/preferences/evaluation_result.rb +61 -0
- data/app/services/command_tower/messaging/preferences/evaluator.rb +194 -0
- data/app/services/command_tower/messaging/preferences/invalid_preference_state_error.rb +9 -0
- data/app/services/command_tower/messaging/preferences/invalid_preference_write_error.rb +10 -0
- data/app/services/command_tower/messaging/preferences/not_settings_visible_error.rb +10 -0
- data/app/services/command_tower/messaging/preferences/preference_state.rb +61 -0
- data/app/services/command_tower/messaging/preferences/reason_classes.rb +17 -0
- data/app/services/command_tower/messaging/preferences/resolve.rb +50 -0
- data/app/services/command_tower/messaging/preferences/store.rb +126 -0
- data/app/services/command_tower/messaging/preferences/store_error.rb +10 -0
- data/app/services/command_tower/messaging/preferences/suppressed_destination.rb +24 -0
- data/app/services/command_tower/messaging/preferences/unknown_type_error.rb +9 -0
- data/app/services/command_tower/messaging/preferences/update.rb +248 -0
- data/app/services/command_tower/messaging/preferences.rb +68 -0
- data/app/services/command_tower/messaging/pushover/adapters/disabled_adapter.rb +33 -0
- data/app/services/command_tower/messaging/pushover/adapters/fake_adapter.rb +90 -0
- data/app/services/command_tower/messaging/pushover/adapters/http_adapter.rb +145 -0
- data/app/services/command_tower/messaging/pushover/adapters/log_adapter.rb +52 -0
- data/app/services/command_tower/messaging/pushover/result.rb +34 -0
- data/app/services/command_tower/messaging/pushover/transport.rb +64 -0
- data/app/services/command_tower/messaging/recipient_readiness/channel_result.rb +55 -0
- data/app/services/command_tower/messaging/recipient_readiness/error.rb +9 -0
- data/app/services/command_tower/messaging/recipient_readiness/evaluate.rb +319 -0
- data/app/services/command_tower/messaging/recipient_readiness/identity_facts.rb +54 -0
- data/app/services/command_tower/messaging/recipient_readiness/reason_codes.rb +33 -0
- data/app/services/command_tower/messaging/recipient_readiness/recipient_not_found_error.rb +9 -0
- data/app/services/command_tower/messaging/recipient_readiness/recipient_result.rb +38 -0
- data/app/services/command_tower/messaging/recipient_readiness/unknown_channel_error.rb +9 -0
- data/app/services/command_tower/messaging/recipient_readiness.rb +26 -0
- data/app/services/command_tower/messaging/rendering/channel_renderer.rb +127 -0
- data/app/services/command_tower/messaging/rendering/render_error.rb +24 -0
- data/app/services/command_tower/messaging/rendering/rendered_payload.rb +44 -0
- data/app/services/command_tower/messaging/rendering/rendered_pushover_payload.rb +43 -0
- data/app/services/command_tower/messaging/rendering/rendered_sms_payload.rb +40 -0
- data/app/services/command_tower/messaging.rb +35 -0
- data/app/services/command_tower/secrets/cleanse.rb +19 -5
- data/app/services/command_tower/secrets/generate.rb +62 -27
- data/app/services/command_tower/secrets/verify.rb +46 -12
- data/app/services/command_tower/secrets.rb +2 -0
- data/app/services/command_tower/service_base.rb +30 -43
- data/app/services/command_tower/service_logging.rb +2 -36
- data/app/services/command_tower/services/account/clear_phone.rb +33 -0
- data/app/services/command_tower/services/account/phone_verification/send.rb +114 -0
- data/app/services/command_tower/services/account/phone_verification/verify.rb +89 -0
- data/app/services/command_tower/services/account/pushover/create.rb +28 -0
- data/app/services/command_tower/services/account/pushover/ct_support.rb +70 -0
- data/app/services/command_tower/services/account/pushover/destroy.rb +30 -0
- data/app/services/command_tower/services/account/pushover/replace.rb +28 -0
- data/app/services/command_tower/services/account/pushover/show.rb +19 -0
- data/app/services/command_tower/services/account/pushover/verify.rb +30 -0
- data/app/services/command_tower/services/account/update_phone.rb +58 -0
- data/app/services/command_tower/services/admin/users.rb +377 -0
- data/app/services/command_tower/services/admin/workspace/manifest.rb +49 -0
- data/app/services/command_tower/services/application_service.rb +58 -0
- data/app/services/command_tower/services/audit/events/filter_options.rb +75 -0
- data/app/services/command_tower/services/audit/events.rb +199 -0
- data/app/services/command_tower/services/auth/assign_default_membership_role.rb +44 -0
- data/app/services/command_tower/services/auth/authenticate_session.rb +132 -0
- data/app/services/command_tower/services/auth/authorize_request.rb +34 -0
- data/app/services/command_tower/services/auth/client_ip_resolver.rb +13 -0
- data/app/services/command_tower/services/auth/email_availability.rb +42 -0
- data/app/services/command_tower/services/auth/email_verification/send.rb +65 -0
- data/app/services/command_tower/services/auth/email_verification/verify.rb +53 -0
- data/app/services/command_tower/services/auth/identity_policy.rb +60 -0
- data/app/services/command_tower/services/auth/password_recovery/rate_limits/check_send.rb +48 -0
- data/app/services/command_tower/services/auth/password_recovery/rate_limits/check_token_issue.rb +35 -0
- data/app/services/command_tower/services/auth/password_recovery/rate_limits/enforcement.rb +53 -0
- data/app/services/command_tower/services/auth/password_recovery_session/create.rb +26 -0
- data/app/services/command_tower/services/auth/password_recovery_session/decode.rb +29 -0
- data/app/services/command_tower/services/auth/password_recovery_session/encode.rb +31 -0
- data/app/services/command_tower/services/auth/password_recovery_session/validate.rb +58 -0
- data/app/services/command_tower/services/auth/password_reset/reset.rb +75 -0
- data/app/services/command_tower/services/auth/password_reset/send.rb +77 -0
- data/app/services/command_tower/services/auth/password_reset/token_verification.rb +68 -0
- data/app/services/command_tower/services/auth/password_reset/validate.rb +29 -0
- data/app/services/command_tower/services/auth/plain_text/login.rb +52 -0
- data/app/services/command_tower/services/auth/principal_capabilities/project.rb +48 -0
- data/app/services/command_tower/services/auth/register.rb +77 -0
- data/app/services/command_tower/services/auth/signup_rate_limits/check_availability.rb +52 -0
- data/app/services/command_tower/services/auth/signup_rate_limits/check_register.rb +24 -0
- data/app/services/command_tower/services/auth/signup_rate_limits/check_token_issue.rb +33 -0
- data/app/services/command_tower/services/auth/signup_rate_limits/enforcement.rb +50 -0
- data/app/services/command_tower/services/auth/signup_session/create.rb +26 -0
- data/app/services/command_tower/services/auth/signup_session/decode.rb +29 -0
- data/app/services/command_tower/services/auth/signup_session/encode.rb +31 -0
- data/app/services/command_tower/services/auth/signup_session/validate.rb +58 -0
- data/app/services/command_tower/services/auth/username_availability.rb +28 -0
- data/app/services/command_tower/services/impersonation/create.rb +29 -0
- data/app/services/command_tower/services/impersonation/end.rb +28 -0
- data/app/services/command_tower/services/impersonation/record_activity.rb +30 -0
- data/app/services/command_tower/services/impersonation/terminate_open_sessions.rb +51 -0
- data/app/services/command_tower/services/me/capabilities.rb +43 -0
- data/app/services/command_tower/services/me/change_password.rb +96 -0
- data/app/services/command_tower/services/me/pushover_product_gate.rb +18 -0
- data/app/services/command_tower/services/me/sms_product_gate.rb +19 -0
- data/app/services/command_tower/services/me/update_name.rb +58 -0
- data/app/services/command_tower/services/messaging/communications/produce.rb +68 -0
- data/app/services/command_tower/services/messaging/communications/produce_many.rb +154 -0
- data/app/services/command_tower/services/messaging/inbox.rb +129 -0
- data/app/services/command_tower/services/messaging/preferences/platform_enabled_channels.rb +20 -0
- data/app/services/command_tower/services/messaging/preferences/show.rb +36 -0
- data/app/services/command_tower/services/messaging/preferences/update.rb +43 -0
- data/app/services/command_tower/services/messaging/recipients.rb +20 -0
- data/app/services/command_tower/services/rate_limits/check.rb +31 -0
- data/app/services/command_tower/services/service_result.rb +73 -0
- data/app/services/command_tower/user_attributes/mutate.rb +154 -0
- data/app/services/command_tower/username/available.rb +24 -11
- data/app/shared_sequences/command_tower/shared_sequences/admin/users/resolve_scoped_user.rb +42 -0
- data/app/views/command_tower/email_verification_mailer/verify_email.html.erb +70 -22
- data/app/views/command_tower/messaging/rendering/email.html.erb +28 -0
- data/app/views/command_tower/messaging/rendering/email.text.erb +7 -0
- data/app/views/command_tower/messaging/rendering/pushover.text.erb +1 -0
- data/app/views/command_tower/messaging/rendering/sms.text.erb +1 -0
- data/app/views/command_tower/password_reset_mailer/reset_password.html.erb +107 -0
- data/app/views/layouts/mailer.html.erb +13 -0
- data/app/views/layouts/mailer.text.erb +1 -0
- data/app/workflows/command_tower/transactional.rb +65 -0
- data/app/workflows/command_tower/workflows/admin/messaging/create_announcement_workflow.rb +73 -0
- data/app/workflows/command_tower/workflows/admin/scope_resolution.rb +17 -0
- data/app/workflows/command_tower/workflows/admin/users/error_mapping.rb +26 -0
- data/app/workflows/command_tower/workflows/admin/users/identity_mutation.rb +30 -0
- data/app/workflows/command_tower/workflows/admin/users/list_assignable_roles_workflow.rb +30 -0
- data/app/workflows/command_tower/workflows/admin/users/list_workflow.rb +54 -0
- data/app/workflows/command_tower/workflows/admin/users/set_email_validated_workflow.rb +45 -0
- data/app/workflows/command_tower/workflows/admin/users/show_workflow.rb +49 -0
- data/app/workflows/command_tower/workflows/admin/users/update_email_workflow.rb +45 -0
- data/app/workflows/command_tower/workflows/admin/users/update_name_workflow.rb +49 -0
- data/app/workflows/command_tower/workflows/admin/users/update_roles_workflow.rb +61 -0
- data/app/workflows/command_tower/workflows/admin/users/update_username_workflow.rb +45 -0
- data/app/workflows/command_tower/workflows/admin/workspace/manifest_workflow.rb +25 -0
- data/app/workflows/command_tower/workflows/application_workflow.rb +207 -0
- data/app/workflows/command_tower/workflows/audit/error_mapping.rb +24 -0
- data/app/workflows/command_tower/workflows/audit/events/filter_options_for_admin_workflow.rb +34 -0
- data/app/workflows/command_tower/workflows/audit/events/filter_options_for_user_workflow.rb +34 -0
- data/app/workflows/command_tower/workflows/audit/events/list_for_admin_workflow.rb +74 -0
- data/app/workflows/command_tower/workflows/audit/events/list_for_user_workflow.rb +45 -0
- data/app/workflows/command_tower/workflows/audit/events/show_for_admin_workflow.rb +54 -0
- data/app/workflows/command_tower/workflows/audit/events/show_for_user_workflow.rb +42 -0
- data/app/workflows/command_tower/workflows/auth/authenticate_request_workflow.rb +41 -0
- data/app/workflows/command_tower/workflows/auth/authentication_response_effects.rb +25 -0
- data/app/workflows/command_tower/workflows/auth/authorize_request_workflow.rb +28 -0
- data/app/workflows/command_tower/workflows/auth/email_availability_workflow.rb +39 -0
- data/app/workflows/command_tower/workflows/auth/email_verification/send_workflow.rb +32 -0
- data/app/workflows/command_tower/workflows/auth/email_verification/verify_workflow.rb +35 -0
- data/app/workflows/command_tower/workflows/auth/identity_error_status.rb +36 -0
- data/app/workflows/command_tower/workflows/auth/identity_policy_workflow.rb +26 -0
- data/app/workflows/command_tower/workflows/auth/logout_workflow.rb +59 -0
- data/app/workflows/command_tower/workflows/auth/password_recovery_session/authenticate_workflow.rb +49 -0
- data/app/workflows/command_tower/workflows/auth/password_recovery_session/create_workflow.rb +39 -0
- data/app/workflows/command_tower/workflows/auth/password_reset/reset_workflow.rb +40 -0
- data/app/workflows/command_tower/workflows/auth/password_reset/send_workflow.rb +45 -0
- data/app/workflows/command_tower/workflows/auth/password_reset/validate_workflow.rb +40 -0
- data/app/workflows/command_tower/workflows/auth/plain_text/login_workflow.rb +48 -0
- data/app/workflows/command_tower/workflows/auth/principal_capabilities/show_workflow.rb +27 -0
- data/app/workflows/command_tower/workflows/auth/register_workflow.rb +115 -0
- data/app/workflows/command_tower/workflows/auth/session/show_workflow.rb +58 -0
- data/app/workflows/command_tower/workflows/auth/session_error_status.rb +31 -0
- data/app/workflows/command_tower/workflows/auth/signup_error_status.rb +31 -0
- data/app/workflows/command_tower/workflows/auth/signup_session/authenticate_workflow.rb +49 -0
- data/app/workflows/command_tower/workflows/auth/signup_session/create_workflow.rb +37 -0
- data/app/workflows/command_tower/workflows/auth/username_availability_workflow.rb +39 -0
- data/app/workflows/command_tower/workflows/impersonation/error_mapping.rb +27 -0
- data/app/workflows/command_tower/workflows/impersonation/start_workflow.rb +97 -0
- data/app/workflows/command_tower/workflows/impersonation/stop_workflow.rb +66 -0
- data/app/workflows/command_tower/workflows/me/change_password_workflow.rb +34 -0
- data/app/workflows/command_tower/workflows/me/clear_phone_workflow.rb +52 -0
- data/app/workflows/command_tower/workflows/me/error_mapping.rb +46 -0
- data/app/workflows/command_tower/workflows/me/error_status.rb +28 -0
- data/app/workflows/command_tower/workflows/me/phone_verification/send_workflow.rb +65 -0
- data/app/workflows/command_tower/workflows/me/phone_verification/verify_workflow.rb +57 -0
- data/app/workflows/command_tower/workflows/me/pushover/create_workflow.rb +38 -0
- data/app/workflows/command_tower/workflows/me/pushover/destroy_workflow.rb +34 -0
- data/app/workflows/command_tower/workflows/me/pushover/replace_workflow.rb +38 -0
- data/app/workflows/command_tower/workflows/me/pushover/show_workflow.rb +34 -0
- data/app/workflows/command_tower/workflows/me/pushover/verify_workflow.rb +34 -0
- data/app/workflows/command_tower/workflows/me/pushover/workflow_support.rb +31 -0
- data/app/workflows/command_tower/workflows/me/show_workflow.rb +33 -0
- data/app/workflows/command_tower/workflows/me/update_name_workflow.rb +49 -0
- data/app/workflows/command_tower/workflows/me/update_phone_workflow.rb +55 -0
- data/app/workflows/command_tower/workflows/messaging/communications/produce_recipient_workflow.rb +60 -0
- data/app/workflows/command_tower/workflows/messaging/error_mapping.rb +26 -0
- data/app/workflows/command_tower/workflows/messaging/execution/deliver_workflow.rb +234 -0
- data/app/workflows/command_tower/workflows/messaging/handoff/process_workflow.rb +90 -0
- data/app/workflows/command_tower/workflows/messaging/inbox.rb +97 -0
- data/app/workflows/command_tower/workflows/messaging/preferences/show_workflow.rb +31 -0
- data/app/workflows/command_tower/workflows/messaging/preferences/update_workflow.rb +37 -0
- data/app/workflows/command_tower/workflows/profile/show_workflow.rb +28 -0
- data/app/workflows/command_tower/workflows/workflow_result.rb +58 -0
- data/config/routes.rb +105 -37
- data/db/migrate/{20241117043720_create_command_tower_users.rb → 20260805000001_create_command_tower_users.rb} +7 -9
- data/db/migrate/{20241204065708_create_command_tower_user_secrets.rb → 20260805000002_create_command_tower_user_secrets.rb} +3 -0
- data/db/migrate/20260805000003_create_command_tower_messaging_core.rb +90 -0
- data/db/migrate/20260805000004_create_messaging_notification_preferences.rb +17 -0
- data/db/migrate/20260805000005_create_messaging_endpoints_and_pushover_credentials.rb +72 -0
- data/db/migrate/20260816000001_create_command_tower_audit_events.rb +45 -0
- data/db/migrate/20260817000001_add_scope_columns_to_command_tower_audit_events.rb +34 -0
- data/db/migrate/20260817000003_create_command_tower_impersonation_sessions.rb +26 -0
- data/docs/admin_workspace.md +158 -0
- data/docs/api_reference.md +599 -0
- data/docs/architecture.md +35 -0
- data/docs/audit.md +195 -0
- data/docs/authentication.md +51 -0
- data/docs/authentication_authorization_guide.md +154 -0
- data/docs/authorization.md +55 -0
- data/docs/change_password_workflow.md +67 -0
- data/docs/controllers.md +41 -0
- data/docs/cookie_authentication_guide.md +167 -0
- data/docs/email_verification_workflow.md +58 -0
- data/docs/eventing.md +179 -0
- data/docs/extending.md +136 -0
- data/docs/host_integration_guide.md +249 -0
- data/docs/initializing.md +169 -0
- data/docs/messaging_integration_guide.md +87 -0
- data/docs/models.md +55 -0
- data/docs/pagination.md +47 -0
- data/docs/password_reset_workflow.md +82 -0
- data/docs/principal_capabilities.md +89 -0
- data/docs/sensitive_routes.md +47 -0
- data/docs/testing.md +74 -0
- data/docs/upgrades/0.10.0.md +58 -0
- data/docs/upgrades/0.11.0.md +92 -0
- data/docs/upgrades/README.md +10 -0
- data/lib/command_tower/admin_scope/apply_audit_scoping.rb +45 -0
- data/lib/command_tower/admin_scope/apply_users_narrowing.rb +21 -0
- data/lib/command_tower/admin_scope/manifest_projection.rb +102 -0
- data/lib/command_tower/admin_scope/resolve.rb +31 -0
- data/lib/command_tower/admin_scope/scope_context.rb +7 -0
- data/lib/command_tower/admin_scope/scope_option.rb +7 -0
- data/lib/command_tower/admin_scope.rb +20 -0
- data/lib/command_tower/admin_workspace.rb +16 -0
- data/lib/command_tower/audit/attribution.rb +90 -0
- data/lib/command_tower/audit/emit.rb +113 -0
- data/lib/command_tower/audit/masking.rb +41 -0
- data/lib/command_tower/audit/payload.rb +128 -0
- data/lib/command_tower/audit/persistence/subscriber.rb +85 -0
- data/lib/command_tower/audit.rb +27 -0
- data/lib/command_tower/authorization/assignable_roles.rb +36 -0
- data/lib/command_tower/authorization/default.yml +101 -35
- data/lib/command_tower/authorization/effective_entity_grants.rb +55 -0
- data/lib/command_tower/authorization/entity.rb +15 -5
- data/lib/command_tower/authorization/role.rb +5 -4
- data/lib/command_tower/authorization.rb +71 -10
- data/lib/command_tower/configuration/admin_scope/config.rb +104 -0
- data/lib/command_tower/configuration/admin_scope/tool_registration.rb +28 -0
- data/lib/command_tower/configuration/application/config.rb +2 -2
- data/lib/command_tower/configuration/authorization/config.rb +6 -0
- data/lib/command_tower/configuration/base.rb +0 -1
- data/lib/command_tower/configuration/config.rb +51 -1
- data/lib/command_tower/configuration/credentials/config.rb +33 -0
- data/lib/command_tower/configuration/credentials/smtp.rb +33 -0
- data/lib/command_tower/configuration/credentials/twilio.rb +33 -0
- data/lib/command_tower/configuration/email/config.rb +1 -18
- data/lib/command_tower/configuration/identity/config.rb +19 -0
- data/lib/command_tower/configuration/identity/phone_verification.rb +54 -0
- data/lib/command_tower/configuration/impersonation/config.rb +38 -0
- data/lib/command_tower/configuration/jwt/config.rb +7 -0
- data/lib/command_tower/configuration/jwt/cookie/config.rb +64 -0
- data/lib/command_tower/configuration/jwt/cookie/csrf/config.rb +72 -0
- data/lib/command_tower/configuration/login/strategy/plain_text/config.rb +6 -0
- data/lib/command_tower/configuration/login/strategy/plain_text/email_verify.rb +5 -0
- data/lib/command_tower/configuration/login/strategy/plain_text/password_reset.rb +49 -0
- data/lib/command_tower/configuration/messaging/config.rb +60 -0
- data/lib/command_tower/configuration/messaging/pushover.rb +42 -0
- data/lib/command_tower/configuration/messaging/sms.rb +32 -0
- data/lib/command_tower/configuration/password_recovery_session/config.rb +61 -0
- data/lib/command_tower/configuration/password_recovery_session/rate_limits.rb +33 -0
- data/lib/command_tower/configuration/registry/admin_workspace/config.rb +199 -0
- data/lib/command_tower/configuration/registry/admin_workspace/tool_definition.rb +150 -0
- data/lib/command_tower/configuration/registry/audit/config.rb +403 -0
- data/lib/command_tower/configuration/registry/audit/event_definition.rb +155 -0
- data/lib/command_tower/configuration/registry/config.rb +34 -0
- data/lib/command_tower/configuration/registry/principal_capabilities/capability_definition.rb +43 -0
- data/lib/command_tower/configuration/registry/principal_capabilities/config.rb +136 -0
- data/lib/command_tower/configuration/signup_session/config.rb +67 -0
- data/lib/command_tower/configuration/signup_session/email_availability.rb +18 -0
- data/lib/command_tower/configuration/signup_session/rate_limits.rb +48 -0
- data/lib/command_tower/configuration/user/config.rb +6 -5
- data/lib/command_tower/credential_resolution/env_backend.rb +25 -0
- data/lib/command_tower/credential_resolution/smtp_action_mailer_bridge.rb +24 -0
- data/lib/command_tower/credential_resolution/smtp_credentials.rb +26 -0
- data/lib/command_tower/credential_resolution/twilio_credentials.rb +26 -0
- data/lib/command_tower/credential_resolution.rb +78 -0
- data/lib/command_tower/current.rb +19 -0
- data/lib/command_tower/engine.rb +23 -5
- data/lib/command_tower/events.rb +184 -0
- data/lib/command_tower/execution.rb +111 -0
- data/lib/command_tower/impersonation/activity_declaration.rb +23 -0
- data/lib/command_tower/impersonation/apply_overlay.rb +88 -0
- data/lib/command_tower/impersonation/clear_overlay_for_audit.rb +23 -0
- data/lib/command_tower/impersonation/establish_identity.rb +44 -0
- data/lib/command_tower/install/baseline.rb +34 -0
- data/lib/command_tower/install/doctor.rb +227 -0
- data/lib/command_tower/jwt/authorization_helper.rb +136 -0
- data/lib/command_tower/jwt/csrf_helper.rb +138 -0
- data/lib/command_tower/logging/lifecycle_declaration.rb +27 -0
- data/lib/command_tower/logging/projection.rb +67 -0
- data/lib/command_tower/logging/subscriber.rb +103 -0
- data/lib/command_tower/password_recovery/authorization_helper.rb +25 -0
- data/lib/command_tower/principal_capabilities.rb +15 -0
- data/lib/command_tower/redis_connection.rb +21 -0
- data/lib/command_tower/schema/error/email_validation_required.rb +15 -0
- data/lib/command_tower/schema/shared/user.rb +32 -0
- data/lib/command_tower/schema.rb +2 -25
- data/lib/command_tower/signup/authorization_helper.rb +25 -0
- data/lib/command_tower/spec_helper.rb +58 -1
- data/lib/command_tower/testing.rb +62 -0
- data/lib/command_tower/version.rb +1 -1
- data/lib/command_tower.rb +32 -0
- data/lib/generators/command_tower/configure/USAGE +14 -2
- data/lib/generators/command_tower/configure/configure_generator.rb +29 -2
- data/lib/tasks/auto_annotate_models.rake +23 -55
- data/lib/tasks/install.rake +81 -0
- data/lib/tasks/users.rake +119 -0
- data/spec/factories/entity.rb +37 -0
- data/spec/factories/impersonation_session.rb +17 -0
- data/spec/factories/messaging.rb +156 -0
- data/spec/factories/role.rb +29 -0
- data/spec/factories/user.rb +69 -0
- data/spec/factories/user_secret.rb +12 -0
- metadata +686 -55
- data/app/controllers/command_tower/admin_controller.rb +0 -110
- data/app/controllers/command_tower/auth/plain_text_controller.rb +0 -133
- data/app/controllers/command_tower/inbox/message_blast_controller.rb +0 -89
- data/app/controllers/command_tower/inbox/message_controller.rb +0 -81
- data/app/controllers/command_tower/user_controller.rb +0 -49
- data/app/controllers/command_tower/username_controller.rb +0 -26
- data/app/helpers/command_tower/pagination_helper.rb +0 -40
- data/app/helpers/command_tower/schema_helper.rb +0 -29
- data/app/models/message.rb +0 -30
- data/app/models/message_blast.rb +0 -27
- data/app/services/command_tower/admin_service/users.rb +0 -27
- data/app/services/command_tower/inbox_service/blast/delete.rb +0 -23
- data/app/services/command_tower/inbox_service/blast/metadata.rb +0 -26
- data/app/services/command_tower/inbox_service/blast/new_user_blaster.rb +0 -24
- data/app/services/command_tower/inbox_service/blast/retrieve.rb +0 -30
- data/app/services/command_tower/inbox_service/blast/upsert.rb +0 -67
- data/app/services/command_tower/inbox_service/message/metadata.rb +0 -41
- data/app/services/command_tower/inbox_service/message/modify.rb +0 -44
- data/app/services/command_tower/inbox_service/message/retrieve.rb +0 -36
- data/app/services/command_tower/inbox_service/message/send.rb +0 -33
- data/app/services/command_tower/jwt/time_delay_token.rb +0 -17
- data/app/services/command_tower/login_strategy/plain_text/create.rb +0 -43
- data/app/services/command_tower/login_strategy/plain_text/email_verification/generate.rb +0 -29
- data/app/services/command_tower/login_strategy/plain_text/email_verification/send.rb +0 -23
- data/app/services/command_tower/login_strategy/plain_text/email_verification/verify.rb +0 -24
- data/app/services/command_tower/login_strategy/plain_text/login.rb +0 -50
- data/app/services/command_tower/pagination_service_helper.rb +0 -88
- data/app/services/command_tower/user_attributes/modify.rb +0 -68
- data/app/services/command_tower/user_attributes/roles.rb +0 -27
- data/db/migrate/20250223023306_create_command_tower_messages.rb +0 -12
- data/db/migrate/20250223023313_create_command_tower_message_blasts.rb +0 -14
- data/lib/command_tower/schema/admin/users.rb +0 -16
- data/lib/command_tower/schema/inbox/blast_request.rb +0 -15
- data/lib/command_tower/schema/inbox/blast_response.rb +0 -16
- data/lib/command_tower/schema/inbox/message_blast_entity.rb +0 -16
- data/lib/command_tower/schema/inbox/message_blast_metadata.rb +0 -16
- data/lib/command_tower/schema/inbox/message_entity.rb +0 -14
- data/lib/command_tower/schema/inbox/metadata.rb +0 -18
- data/lib/command_tower/schema/inbox/modified.rb +0 -13
- data/lib/command_tower/schema/page.rb +0 -11
- data/lib/command_tower/schema/pagination.rb +0 -16
- data/lib/command_tower/schema/plain_text/create_user_request.rb +0 -18
- data/lib/command_tower/schema/plain_text/create_user_response.rb +0 -17
- data/lib/command_tower/schema/plain_text/email_verify_request.rb +0 -11
- data/lib/command_tower/schema/plain_text/email_verify_response.rb +0 -11
- data/lib/command_tower/schema/plain_text/email_verify_send_request.rb +0 -9
- data/lib/command_tower/schema/plain_text/email_verify_send_response.rb +0 -11
- data/lib/command_tower/schema/plain_text/login_request.rb +0 -15
- data/lib/command_tower/schema/plain_text/login_response.rb +0 -16
- data/lib/command_tower/schema/user.rb +0 -28
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Password Reset (Forgot Password) Workflow
|
|
2
|
+
|
|
3
|
+
Public password recovery for users who cannot authenticate. Flow uses a **password-recovery session** plus gated **password-reset** endpoints. Detailed schemas live here and are summarized in the [API reference](api_reference.md).
|
|
4
|
+
|
|
5
|
+
## Purpose and orchestration
|
|
6
|
+
|
|
7
|
+
| Step | Route | Workflow |
|
|
8
|
+
|------|-------|----------|
|
|
9
|
+
| Create recovery session | `POST /auth/password-recovery-session` | `Workflows::Auth::PasswordRecoverySession::CreateWorkflow` |
|
|
10
|
+
| Send reset email | `POST /auth/password-reset/send` | `Workflows::Auth::PasswordReset::SendWorkflow` |
|
|
11
|
+
| Validate token | `POST /auth/password-reset/validate` | `Workflows::Auth::PasswordReset::ValidateWorkflow` |
|
|
12
|
+
| Reset password | `POST /auth/password-reset/reset` | `Workflows::Auth::PasswordReset::ResetWorkflow` |
|
|
13
|
+
|
|
14
|
+
All listed workflows inherit `ApplicationWorkflow` with `retry_strategy :none`. Controllers under `CommandTower::Auth::PasswordReset::*` and password-recovery-session authenticate the recovery session where required, deserialize, run **one** workflow, and render `WorkflowResult`.
|
|
15
|
+
|
|
16
|
+
**Feature gate:** `CommandTower.config.login.plain_text.password_reset?` (reset endpoints). Recovery session creation is part of the public recovery path.
|
|
17
|
+
|
|
18
|
+
**Boundary:** Controllers own transport + recovery-session auth. Workflows orchestrate rate limits and password-reset services. Services own email/token capability details. Do not call messaging execution internals from hosts — see [extending.md](extending.md).
|
|
19
|
+
|
|
20
|
+
## High-level characteristics
|
|
21
|
+
|
|
22
|
+
- **No user JWT**; `password-reset/send` requires a valid **password-recovery session**; `validate` / `reset` are public and use the emailed reset `token` in the body
|
|
23
|
+
- Email-based identification; responses avoid user enumeration where designed to
|
|
24
|
+
- Secure, time-limited reset tokens
|
|
25
|
+
- Rate limiting via password-recovery rate-limit services
|
|
26
|
+
|
|
27
|
+
## Endpoints (engine-relative)
|
|
28
|
+
|
|
29
|
+
### 1. Create password-recovery session
|
|
30
|
+
|
|
31
|
+
`POST /auth/password-recovery-session`
|
|
32
|
+
|
|
33
|
+
Establishes the recovery session used by subsequent reset calls. See request specs under `spec/requests/` for exact payload/envelope.
|
|
34
|
+
|
|
35
|
+
### 2. Send reset email
|
|
36
|
+
|
|
37
|
+
`POST /auth/password-reset/send`
|
|
38
|
+
|
|
39
|
+
**Auth:** Password-recovery session
|
|
40
|
+
|
|
41
|
+
**Body (typical):** `{ "email": "string" }`
|
|
42
|
+
|
|
43
|
+
**Workflow:** `SendWorkflow` → rate-limit check → `Services::Auth::PasswordReset::Send`
|
|
44
|
+
|
|
45
|
+
### 3. Validate token
|
|
46
|
+
|
|
47
|
+
`POST /auth/password-reset/validate`
|
|
48
|
+
|
|
49
|
+
**Auth:** Public (reset token in body)
|
|
50
|
+
|
|
51
|
+
**Body (typical):** `{ "token": "string" }` (optional `email` when config requires it)
|
|
52
|
+
|
|
53
|
+
**Workflow:** `ValidateWorkflow`
|
|
54
|
+
|
|
55
|
+
### 4. Reset password
|
|
56
|
+
|
|
57
|
+
`POST /auth/password-reset/reset`
|
|
58
|
+
|
|
59
|
+
**Auth:** Public (reset token in body)
|
|
60
|
+
|
|
61
|
+
**Body (typical):** token + new password fields (`passwordConfirmation` / `password_confirmation`)
|
|
62
|
+
|
|
63
|
+
**Workflow:** `ResetWorkflow`
|
|
64
|
+
|
|
65
|
+
Success behavior and verifier rotation differ from authenticated [change password](change_password_workflow.md) — treat recovery success per product policy (often force Sign In).
|
|
66
|
+
|
|
67
|
+
## Errors
|
|
68
|
+
|
|
69
|
+
Failures return the standard application envelope via `render_application_result`. Status mapping uses Auth identity/password-reset error helpers. Validation failures from deserializers typically surface as `422 Unprocessable Entity`.
|
|
70
|
+
|
|
71
|
+
## Security notes
|
|
72
|
+
|
|
73
|
+
- Never log passwords, raw tokens, or recovery-session secrets
|
|
74
|
+
- Prefer HTTPS in production
|
|
75
|
+
- Respect rate limits; do not probe for account existence in client UX
|
|
76
|
+
|
|
77
|
+
## Related
|
|
78
|
+
|
|
79
|
+
- [Change password workflow](change_password_workflow.md)
|
|
80
|
+
- [API reference](api_reference.md)
|
|
81
|
+
- [Initializing](initializing.md) — secrets / feature config
|
|
82
|
+
- [Extending](extending.md)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Principal capabilities
|
|
2
|
+
|
|
3
|
+
Authenticated **frontend-projectable** capability ids for the current principal. Projection is `effective RBAC entity grants ∩ curated registry` — never role/group names, and never an auto-dump of `Entity.entities`.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
RBAC groups → entity grants → curated projectable registry
|
|
7
|
+
→ possessed principalCapabilities → shared FE gating (4.6.2+)
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
CommandTower owns which CT entities are frontend-projectable. Hosts own who receives those entities through RBAC group composition. Hosts register **host-owned** projectables only; they do **not** re-register or redefine CT capability ids.
|
|
11
|
+
|
|
12
|
+
## Register (configuration)
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
CommandTower.configure do |config|
|
|
16
|
+
# Default 1:1 — id and required_entity share the same name
|
|
17
|
+
config.registry.principal_capabilities.capability :admin_workspace
|
|
18
|
+
|
|
19
|
+
# Host additive (example) — override required_entity when names differ
|
|
20
|
+
config.registry.principal_capabilities.capability :manage_wagers do |capability|
|
|
21
|
+
capability.required_entity = :pickem_admin_wagers
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Field | Rules |
|
|
27
|
+
|-------|--------|
|
|
28
|
+
| id | DSL name; public contract |
|
|
29
|
+
| required_entity | RBAC entity name; defaults to id when omitted; must exist after RBAC composition |
|
|
30
|
+
| owner | `:command_tower` (seeded) or `:host` (host registration) |
|
|
31
|
+
|
|
32
|
+
Lookup: `CommandTower.config.registry.principal_capabilities.fetch(:admin_workspace)`.
|
|
33
|
+
|
|
34
|
+
## Seeded CommandTower catalog (4.6.1 + 4.7.1 + 6.3)
|
|
35
|
+
|
|
36
|
+
| id / required_entity | Why projectable |
|
|
37
|
+
|----------------------|-----------------|
|
|
38
|
+
| `admin_workspace` | Gate Admin Workspace affordances without probing `/admin/workspace` |
|
|
39
|
+
| `admin_users` | Gate Admin Users Collection/detail presentation |
|
|
40
|
+
| `admin_users_update` | Gate Admin Users identity mutations (name, username, email, email validation) |
|
|
41
|
+
| `admin_rbac_assignments` | Gate Admin Users role assignment (assignable catalog + PATCH roles) |
|
|
42
|
+
| `admin_audit_events` | Gate Audit Admin affordances and deep links outside the workspace manifest |
|
|
43
|
+
| `admin_messaging_announcements` | Gate Messaging Admin affordances outside the manifest |
|
|
44
|
+
| `admin_impersonation` | Gate Impersonate affordances (5.6 composition). Possession does not start a session by itself. |
|
|
45
|
+
| `me_audit_events` | Gate Account **Activity** / self-audit Explorer presentation (4.7.1). Hosts own who receives the entity. |
|
|
46
|
+
|
|
47
|
+
Me/Auth route gates (`session`, `me`, …) and unimplemented Admin entities are **not** seeded. Adding a new CT RBAC entity later requires an explicit slice decision to also register a principal capability.
|
|
48
|
+
|
|
49
|
+
## Boot validation
|
|
50
|
+
|
|
51
|
+
1. Host `CommandTower.configure` may register additive capabilities (mutable).
|
|
52
|
+
2. `after_initialize` — `principal_capabilities.finalize!` with audit / admin_workspace, then freeze (skipped in test).
|
|
53
|
+
3. `to_prepare` — after RBAC, `validate_required_entities!(Entity.entities)`.
|
|
54
|
+
|
|
55
|
+
## Runtime HTTP
|
|
56
|
+
|
|
57
|
+
`GET /auth/principal-capabilities` (engine-relative; hosts mounting at `/api` use `GET /api/auth/principal-capabilities`).
|
|
58
|
+
|
|
59
|
+
- Authn + authz: entity `principal_capabilities` (`PrincipalCapabilitiesController#show`). Grant to host `member` (and operators that need the call) like other Me/Auth surfaces.
|
|
60
|
+
- Controller → `Workflows::Auth::PrincipalCapabilities::ShowWorkflow` (`retry_strategy :none`) → `Services::Auth::PrincipalCapabilities::Project` → `PrincipalCapabilitiesSerializer`.
|
|
61
|
+
- Service: if any role has `allow_everything`, return **all** registered projectable ids; else include definitions whose `required_entity` is granted. Unique + sorted. No role-name checks.
|
|
62
|
+
- Envelope `data`: `{ principalCapabilities: string[] }` — possessed ids only.
|
|
63
|
+
|
|
64
|
+
| Persona (typical host composition) | Example `principalCapabilities` |
|
|
65
|
+
|------------------------------------|---------------------------------|
|
|
66
|
+
| guest | **401** |
|
|
67
|
+
| email verification required | **412** |
|
|
68
|
+
| `member` (host grants `me_audit_events`) | `me_audit_events` |
|
|
69
|
+
| `member` (host withholds `me_audit_events`) | `[]` |
|
|
70
|
+
| `audit_operator` | `admin_audit_events`, `admin_workspace` |
|
|
71
|
+
| `messaging_operator` | `admin_messaging_announcements`, `admin_workspace` |
|
|
72
|
+
| host `admin` / `operations_admin` | CT Admin seeds (not necessarily `me_audit_events`) |
|
|
73
|
+
| `owner` | all **registered** ids (CT + host additive) |
|
|
74
|
+
|
|
75
|
+
## Naming collisions (different concepts)
|
|
76
|
+
|
|
77
|
+
| Name | Meaning |
|
|
78
|
+
|------|---------|
|
|
79
|
+
| `principalCapabilities` / this registry | Runtime possessed, frontend-projectable RBAC-backed facts |
|
|
80
|
+
| `/me` `capabilities` | Account self-service flags — **not** this projection |
|
|
81
|
+
| HostConfig / build-time `capabilities` | Compile-time product modules — **not** this projection |
|
|
82
|
+
| RBAC group / role names | Host privilege composition — **never** the FE gating contract |
|
|
83
|
+
|
|
84
|
+
## Related
|
|
85
|
+
|
|
86
|
+
- [API reference](api_reference.md#get-authprincipal-capabilities)
|
|
87
|
+
- [Authorization](authorization.md)
|
|
88
|
+
- [Admin Workspace](admin_workspace.md) — tool manifest only; not a UI permission probe
|
|
89
|
+
- [Host integration](host_integration_guide.md)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Sensitive changes and session invalidation
|
|
2
|
+
|
|
3
|
+
CommandTower JWTs are bound to the authenticated user’s **verifier token**. When the verifier rotates, previously issued access tokens that carried the old verifier no longer authenticate (`401`).
|
|
4
|
+
|
|
5
|
+
This is the platform mechanism behind “logout everywhere” and post-password-change session invalidation.
|
|
6
|
+
|
|
7
|
+
## Verifier token on `User`
|
|
8
|
+
|
|
9
|
+
Each `User` may store:
|
|
10
|
+
|
|
11
|
+
- `verifier_token` — opaque string embedded in JWTs at issue time
|
|
12
|
+
- `verifier_token_last_reset` — timestamp of the last rotation
|
|
13
|
+
|
|
14
|
+
API:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
user.reset_verifier_token! # generates a new token, updates timestamp, returns the value
|
|
18
|
+
user.retreive_verifier_token! # returns existing token or creates one (historical spelling)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Authentication compares the verifier claim in the JWT to the user’s current `verifier_token`. A mismatch fails authentication.
|
|
22
|
+
|
|
23
|
+
## When the platform rotates the verifier
|
|
24
|
+
|
|
25
|
+
| Event | Behavior |
|
|
26
|
+
|-------|----------|
|
|
27
|
+
| Authenticated password change (`PATCH /me/password`) | Rotates verifier so prior tokens fail |
|
|
28
|
+
| Host / ops call `user.reset_verifier_token!` | Explicit revoke-all-sessions |
|
|
29
|
+
|
|
30
|
+
There is no engine HTTP admin “mutate user with `verifier_token: true`” route. `UserAttributes::Mutate` may still exist for internal/host use; do not assume a public SchemaHelper admin path.
|
|
31
|
+
|
|
32
|
+
Browser `POST /auth/logout` clears cookie/session transport for that client; it does **not** by itself rotate `verifier_token` for all devices. Use verifier rotation when all outstanding JWTs must die.
|
|
33
|
+
|
|
34
|
+
## Host guidance
|
|
35
|
+
|
|
36
|
+
- Treat password changes and explicit “revoke all sessions” as **sensitive** operations that should rotate the verifier.
|
|
37
|
+
- Do not expose `verifier_token` in API payloads (platform serializers omit it).
|
|
38
|
+
- Prefer platform workflows/services for password change rather than hand-editing password digests without rotation.
|
|
39
|
+
- `UserSecret` backs related validation/recovery secrets; see [Models](models.md).
|
|
40
|
+
|
|
41
|
+
## Related
|
|
42
|
+
|
|
43
|
+
- [Change password workflow](change_password_workflow.md)
|
|
44
|
+
- [Authentication](authentication.md) / [Authentication & authorization guide](authentication_authorization_guide.md)
|
|
45
|
+
- [Cookie authentication](cookie_authentication_guide.md)
|
|
46
|
+
- [Models](models.md)
|
|
47
|
+
- [README](../README.md)
|
data/docs/testing.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Testing with CommandTower
|
|
2
|
+
|
|
3
|
+
CommandTower ships FactoryBot definitions for engine-owned models. Hosts load
|
|
4
|
+
them explicitly in the test suite — there is no production Railtie and
|
|
5
|
+
FactoryBot is not a runtime gem dependency.
|
|
6
|
+
|
|
7
|
+
Back to [README](../README.md). See also [Initializing](initializing.md).
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# spec/rails_helper.rb (after Rails is loaded)
|
|
13
|
+
require "command_tower/testing"
|
|
14
|
+
|
|
15
|
+
CommandTower::Testing.install! # preferred
|
|
16
|
+
# CommandTower::Testing.load_factories! # factories-only / advanced
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Call `install!` **before** any host `FactoryBot.modify` blocks so shared
|
|
20
|
+
factories exist when you extend them.
|
|
21
|
+
|
|
22
|
+
## Extending factories
|
|
23
|
+
|
|
24
|
+
Hosts must not redefine CommandTower base factories. Use `FactoryBot.modify`:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
FactoryBot.modify do
|
|
28
|
+
factory :user do
|
|
29
|
+
roles { ["member"] }
|
|
30
|
+
|
|
31
|
+
trait :operator do
|
|
32
|
+
roles { %w[member operator] }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Packaging and evolution
|
|
39
|
+
|
|
40
|
+
Factory files live under `spec/factories/**` inside the gem and are included in
|
|
41
|
+
the gemspec. Adding or changing a shared factory is a normal CommandTower gem
|
|
42
|
+
bump — hosts pick it up on upgrade.
|
|
43
|
+
|
|
44
|
+
`CommandTower::Testing.load_factories!` is idempotent (safe to call more than
|
|
45
|
+
once). It loads each factory file under `CommandTower::Engine.root/spec/factories`
|
|
46
|
+
explicitly; it does not append to global `FactoryBot.definition_file_paths`.
|
|
47
|
+
|
|
48
|
+
## Responsibilities
|
|
49
|
+
|
|
50
|
+
| Owner | Responsibility |
|
|
51
|
+
|-------|----------------|
|
|
52
|
+
| CommandTower | Base factories for CT-owned models; Testing API; packaging |
|
|
53
|
+
| Host | `install!` in the test boot path; product traits via `FactoryBot.modify` |
|
|
54
|
+
|
|
55
|
+
## Factory invariant
|
|
56
|
+
|
|
57
|
+
Base factories construct **persistent model state only**. They must not invoke
|
|
58
|
+
Services, Workflows, ApplicationWorkflows, or external adapters. Endpoint
|
|
59
|
+
ciphertext uses `SecretBox` / `Fingerprinter` (crypto helpers).
|
|
60
|
+
|
|
61
|
+
For endpoint factories in environments where
|
|
62
|
+
`COMMAND_TOWER_MESSAGING_ENDPOINT_SECRET` is unset, you may call:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
CommandTower::Testing.ensure_endpoint_secret!
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
(SecretBox already falls back to a non-production test secret outside production.)
|
|
69
|
+
|
|
70
|
+
## Contributors vs hosts
|
|
71
|
+
|
|
72
|
+
This guide is the **host Testing API**. Contributors working inside the CommandTower
|
|
73
|
+
repository also follow workspace test standards (`.cursor/rules/testing.mdc`) and
|
|
74
|
+
architecture guards under `spec/architecture/`.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Upgrade: CommandTower 0.10.0
|
|
2
|
+
|
|
3
|
+
**From:** `0.5.0` (and earlier SchemaHelper-era hosts on `main`)
|
|
4
|
+
**To:** `0.10.0`
|
|
5
|
+
|
|
6
|
+
This document summarizes **all changes from `main` through the `0.10.0` release** (committed work on `schematizing` plus the modern platform working tree). It is the durable upgrade / change SST for hosts. For a sequenced blank-host path, use [Host integration](../host_integration_guide.md).
|
|
7
|
+
|
|
8
|
+
## Breaking / host-impact
|
|
9
|
+
|
|
10
|
+
| Change | Host impact |
|
|
11
|
+
|--------|-------------|
|
|
12
|
+
| SchemaHelper Admin / User / legacy Inbox HTTP removed | No `GET /user`, `/user/modify`, `/admin/modify*`, legacy blast/message SchemaHelper routes |
|
|
13
|
+
| Modern response envelope | Engine HTTP returns `{ data, meta, errors }` via `render_application_result` |
|
|
14
|
+
| Current-user path | Use `GET /me` (and `GET /profile`); not `GET /user` |
|
|
15
|
+
| Login path | `POST /auth/plain-text/login` when `login.plain_text.enable?` (route absent → **404** if gate off) |
|
|
16
|
+
| Password change | `PATCH /me/password` (rotates verifier; does not re-issue JWT) |
|
|
17
|
+
| Host RBAC required | Host `rbac_groups.yml` must grant CT-owned Me/Auth entity **names** to product roles; do not copy CT controller mappings. Conflicts fail at boot. |
|
|
18
|
+
| Feature gates | Login, email verify, password reset, availability routes are config-gated |
|
|
19
|
+
| Schema ownership (**AD-SCH-01**) | CT authors migrations; hosts install via `command_tower:install` / `install:migrations` then `db:migrate` |
|
|
20
|
+
| Admin HTTP | Only `POST /admin/messaging/announcements` (no SchemaHelper user admin) |
|
|
21
|
+
|
|
22
|
+
## Platform capabilities in 0.10.0
|
|
23
|
+
|
|
24
|
+
- **Auth:** register, plain-text login, logout, session, identity policy, signup session, email/username availability, email verification, password-recovery session + password reset
|
|
25
|
+
- **Me:** account show, profile, name, password, inbox (list/detail/bulk), preferences, phone, Pushover
|
|
26
|
+
- **Messaging emit:** `Communications::Produce` / `ProduceMany` from host workflows
|
|
27
|
+
- **Install:** `command_tower:install`, configure generator, `command_tower:doctor`
|
|
28
|
+
- **Testing:** `CommandTower::Testing.install!` shared factories
|
|
29
|
+
- **Architecture:** Controller → Workflow → Service; Engine uses `authenticate_request!` / `authorize_request!`
|
|
30
|
+
|
|
31
|
+
## Documentation
|
|
32
|
+
|
|
33
|
+
- Canonical HTTP catalog: [API reference](../api_reference.md)
|
|
34
|
+
- Host start-here: [Host integration](../host_integration_guide.md)
|
|
35
|
+
- Install SST: [Initializing](../initializing.md)
|
|
36
|
+
- Auth/cookie/RBAC guides refreshed to modern routes
|
|
37
|
+
- Messaging and pagination aligned to Inbox + Produce contracts
|
|
38
|
+
|
|
39
|
+
## Upgrade checklist (existing hosts)
|
|
40
|
+
|
|
41
|
+
1. Bump gem to `0.10.0` and run `bin/rails command_tower:install:migrations` (or `SKIP_CONFIGURE=1 bin/rails command_tower:install`) then `bin/rails db:migrate`.
|
|
42
|
+
2. Set JWT + signup/recovery session secrets; run `bin/rails command_tower:doctor`.
|
|
43
|
+
3. Add host `rbac_groups.yml` with a product role (typically `member`) that **grants** CT-owned entity names; do not redefine engine `owner` or copy CT controller mappings. Operational Admin roles are host-owned.
|
|
44
|
+
4. Ensure users who need Me surfaces have the host `member` role (or equivalent).
|
|
45
|
+
5. Enable feature gates you rely on (`login.plain_text.enable`, password reset, email verify, availability).
|
|
46
|
+
6. Point clients at modern paths (`/me`, `/auth/plain-text/login`, envelope parsing).
|
|
47
|
+
7. Smoke: Bearer login → `GET /me` returns **200** (not **403**).
|
|
48
|
+
8. If emitting messages: catalog, channels, adapters; call Produce from host workflows.
|
|
49
|
+
|
|
50
|
+
New hosts: follow [Host integration](../host_integration_guide.md) end-to-end.
|
|
51
|
+
|
|
52
|
+
## Related
|
|
53
|
+
|
|
54
|
+
- [Initializing](../initializing.md)
|
|
55
|
+
- [Extending](../extending.md)
|
|
56
|
+
- [Messaging](../messaging_integration_guide.md)
|
|
57
|
+
- [Authentication & authorization guide](../authentication_authorization_guide.md)
|
|
58
|
+
- [README](../../README.md)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Upgrade: CommandTower 0.11.0
|
|
2
|
+
|
|
3
|
+
**From:** `0.10.0`
|
|
4
|
+
**To:** `0.11.0`
|
|
5
|
+
|
|
6
|
+
This document summarizes **host-visible changes from `0.10.0` through the `0.11.0` release**. It is the durable upgrade / change SST for hosts already on the Auth/Me/Messaging platform. For a sequenced blank-host path, use [Host integration](../host_integration_guide.md).
|
|
7
|
+
|
|
8
|
+
## Breaking / host-impact
|
|
9
|
+
|
|
10
|
+
| Change | Host impact |
|
|
11
|
+
|--------|-------------|
|
|
12
|
+
| New migrations | Install and migrate audit events (+ scope columns) and impersonation sessions via `command_tower:install:migrations` then `db:migrate` |
|
|
13
|
+
| New RBAC entities | Hosts must **explicitly grant** new CT-owned entity names; they are **not** auto-added to a generic CT admin role |
|
|
14
|
+
| `GET /auth/principal-capabilities` | Frontend gating should use possessed projectable ids from this endpoint — **not** role names and **not** `GET /admin/workspace` as a permission probe |
|
|
15
|
+
| Admin HTTP during impersonation | Admin routes return **418** while an impersonation session is active |
|
|
16
|
+
| Admin least privilege | Operational Admin privilege is host-owned role composition; CT ships entities only |
|
|
17
|
+
| Optional Admin resource scoping | Hosts that need scoped Users/Audit register `config.admin_scope` and may set `scope_required` on workspace tools |
|
|
18
|
+
|
|
19
|
+
## Platform capabilities in 0.11.0
|
|
20
|
+
|
|
21
|
+
(In addition to everything in [0.10.0](0.10.0.md).)
|
|
22
|
+
|
|
23
|
+
- **Execution context + eventing:** workflow/service lifecycle on `ActiveSupport::Notifications`; structured logging is a subscriber (not a second bus)
|
|
24
|
+
- **Audit ledger:** append-only `command_tower_audit_events`; registered `audit(...)` on workflows/services; Me + Admin query HTTP + filter-options
|
|
25
|
+
- **Admin Workspace:** tool registry + RBAC-filtered `GET /admin/workspace` manifest; optional host `admin_scope`
|
|
26
|
+
- **Principal capabilities:** curated FE-projectable ids via `GET /auth/principal-capabilities` (effective grants ∩ registry)
|
|
27
|
+
- **Impersonation:** start via Admin Users, end via `DELETE /auth/impersonation-session`; overlay identity; idle/absolute timeouts
|
|
28
|
+
- **Admin Users:** list/show, identity update, assignable roles, RBAC assignment — host-granted entities only
|
|
29
|
+
|
|
30
|
+
### New engine HTTP (engine-relative)
|
|
31
|
+
|
|
32
|
+
| Method | Path |
|
|
33
|
+
|--------|------|
|
|
34
|
+
| GET | `/auth/principal-capabilities` |
|
|
35
|
+
| DELETE | `/auth/impersonation-session` |
|
|
36
|
+
| GET | `/me/audit-events`, `/me/audit-events/:id`, `/me/audit-events/filter-options` |
|
|
37
|
+
| GET | `/admin/workspace` |
|
|
38
|
+
| GET | `/admin/audit-events`, `/admin/audit-events/:id`, `/admin/audit-events/filter-options` |
|
|
39
|
+
| GET | `/admin/users`, `/admin/users/:id`, `/admin/users/assignable-roles` |
|
|
40
|
+
| PATCH | `/admin/users/:id/name`, `/username`, `/email`, `/email-validation`, `/roles` |
|
|
41
|
+
| POST | `/admin/users/:id/impersonation-sessions` |
|
|
42
|
+
|
|
43
|
+
### New RBAC entities (host grants required)
|
|
44
|
+
|
|
45
|
+
| Entity | Typical grant |
|
|
46
|
+
|--------|----------------|
|
|
47
|
+
| `principal_capabilities` | `member` (and operators that need the call) |
|
|
48
|
+
| `me_audit_events` | `member` when Account Activity is in product |
|
|
49
|
+
| `admin_workspace` | host operator roles |
|
|
50
|
+
| `admin_audit_events` | audit operators |
|
|
51
|
+
| `admin_users` | users operators |
|
|
52
|
+
| `admin_users_update` | identity-update operators |
|
|
53
|
+
| `admin_rbac_assignments` | RBAC assignment operators |
|
|
54
|
+
| `admin_impersonation` | impersonation operators |
|
|
55
|
+
|
|
56
|
+
See dummy composition in `rails_app/config/rbac_groups.yml`.
|
|
57
|
+
|
|
58
|
+
### Migrations
|
|
59
|
+
|
|
60
|
+
- `20260816000001_create_command_tower_audit_events.rb`
|
|
61
|
+
- `20260817000001_add_scope_columns_to_command_tower_audit_events.rb`
|
|
62
|
+
- `20260817000003_create_command_tower_impersonation_sessions.rb`
|
|
63
|
+
|
|
64
|
+
## Documentation
|
|
65
|
+
|
|
66
|
+
- [Eventing](../eventing.md)
|
|
67
|
+
- [Audit](../audit.md)
|
|
68
|
+
- [Admin Workspace](../admin_workspace.md)
|
|
69
|
+
- [Principal capabilities](../principal_capabilities.md)
|
|
70
|
+
- Host start-here: [Host integration](../host_integration_guide.md)
|
|
71
|
+
- Canonical HTTP catalog: [API reference](../api_reference.md)
|
|
72
|
+
|
|
73
|
+
## Upgrade checklist (existing 0.10.0 hosts)
|
|
74
|
+
|
|
75
|
+
1. Bump gem to `0.11.0` and run `bin/rails command_tower:install:migrations` then `bin/rails db:migrate`.
|
|
76
|
+
2. Run `bin/rails command_tower:doctor`.
|
|
77
|
+
3. Grant `principal_capabilities` (and `me_audit_events` if Account Activity is in product) on host `member` / equivalent.
|
|
78
|
+
4. Explicitly grant Admin entities to **host-owned** operator roles (`admin_workspace`, `admin_users`, `admin_users_update`, `admin_rbac_assignments`, `admin_audit_events`, `admin_impersonation`) — do not assume a platform `admin` role.
|
|
79
|
+
5. Point Admin FE gating at `GET /auth/principal-capabilities`, not `GET /admin/workspace`.
|
|
80
|
+
6. Optional: register host Admin Workspace tools, `admin_scope`, host audit events, and impersonation timeouts.
|
|
81
|
+
7. Smoke: login → `GET /auth/principal-capabilities` **200**; operator with grants → `GET /admin/workspace` **200**; without grants → **403**.
|
|
82
|
+
8. Smoke: start impersonation → Admin routes **418**; `DELETE /auth/impersonation-session` restores actor.
|
|
83
|
+
|
|
84
|
+
New hosts: follow [Host integration](../host_integration_guide.md) end-to-end (includes 0.10.0 foundations plus this release).
|
|
85
|
+
|
|
86
|
+
## Related
|
|
87
|
+
|
|
88
|
+
- [Initializing](../initializing.md)
|
|
89
|
+
- [Extending](../extending.md)
|
|
90
|
+
- [Authentication & authorization guide](../authentication_authorization_guide.md)
|
|
91
|
+
- [README](../../README.md)
|
|
92
|
+
- Prior release: [0.10.0](0.10.0.md)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Upgrades
|
|
2
|
+
|
|
3
|
+
Host-facing upgrade / change summaries for CommandTower releases.
|
|
4
|
+
|
|
5
|
+
| Version | Summary |
|
|
6
|
+
|---------|---------|
|
|
7
|
+
| [0.11.0](0.11.0.md) | Execution context, audit ledger, Admin Workspace, principal capabilities, impersonation, Admin Users |
|
|
8
|
+
| [0.10.0](0.10.0.md) | Modern Auth/Me/Messaging platform; SchemaHelper removal; host RBAC required |
|
|
9
|
+
|
|
10
|
+
New hosts should start with [Host integration](../host_integration_guide.md).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module AdminScope
|
|
5
|
+
module ApplyAuditScoping
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(relation:, scope_context:, principal:)
|
|
9
|
+
return relation if scope_context.nil?
|
|
10
|
+
|
|
11
|
+
registration = CommandTower.config.admin_scope.fetch(scope_context.tool_id)
|
|
12
|
+
global_event_names = global_visible_event_names
|
|
13
|
+
affected_user_ids = registration.affected_users_in_scope.call(
|
|
14
|
+
scope_value: scope_context.scope_value,
|
|
15
|
+
principal:,
|
|
16
|
+
tool_id: scope_context.tool_id
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
host_relation = registration.narrow_audit.call(
|
|
20
|
+
relation: relation.where(scope_class: CommandTower::Audit::Event::SCOPE_CLASSES[:host]),
|
|
21
|
+
scope_value: scope_context.scope_value,
|
|
22
|
+
principal:,
|
|
23
|
+
tool_id: scope_context.tool_id
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
global_relation = relation.where(
|
|
27
|
+
scope_class: CommandTower::Audit::Event::SCOPE_CLASSES[:global],
|
|
28
|
+
action: global_event_names,
|
|
29
|
+
affected_user_id: affected_user_ids
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
relation.where(id: host_relation.select(:id))
|
|
33
|
+
.or(relation.where(id: global_relation.select(:id)))
|
|
34
|
+
.where.not(scope_class: CommandTower::Audit::Event::SCOPE_CLASSES[:legacy])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def global_visible_event_names
|
|
38
|
+
CommandTower.config.registry.audit.definitions.filter_map do |name, definition|
|
|
39
|
+
name if definition.global_visible_in_host_scope?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
private_class_method :global_visible_event_names
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module AdminScope
|
|
5
|
+
module ApplyUsersNarrowing
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(relation:, scope_context:, principal:)
|
|
9
|
+
return relation if scope_context.nil?
|
|
10
|
+
|
|
11
|
+
registration = CommandTower.config.admin_scope.fetch(scope_context.tool_id)
|
|
12
|
+
registration.narrow_users.call(
|
|
13
|
+
relation:,
|
|
14
|
+
scope_value: scope_context.scope_value,
|
|
15
|
+
principal:,
|
|
16
|
+
tool_id: scope_context.tool_id
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module AdminScope
|
|
5
|
+
module ManifestProjection
|
|
6
|
+
IMPERSONATION_DISABLED_REASON = "Admin tools are unavailable while impersonating a user."
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def call(definition:, principal:)
|
|
11
|
+
scope_payload = scope_payload_for(definition)
|
|
12
|
+
unscoped_unregistered = scope_payload.nil? && !admin_scope_registered?(definition.id)
|
|
13
|
+
|
|
14
|
+
availability = availability_for(definition, principal:)
|
|
15
|
+
scope_options = scope_options_for(definition, principal:)
|
|
16
|
+
|
|
17
|
+
payload = {
|
|
18
|
+
scope: scope_payload,
|
|
19
|
+
availability:,
|
|
20
|
+
scope_options:
|
|
21
|
+
}.compact
|
|
22
|
+
|
|
23
|
+
if CommandTower::Current.impersonation_active
|
|
24
|
+
payload[:availability] = {
|
|
25
|
+
enabled: false,
|
|
26
|
+
reason: IMPERSONATION_DISABLED_REASON
|
|
27
|
+
}
|
|
28
|
+
return payload
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
return {} if unscoped_unregistered
|
|
32
|
+
|
|
33
|
+
payload
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def scope_payload_for(definition)
|
|
37
|
+
return unless definition.scope_required?
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
required: true,
|
|
41
|
+
parameter: camelize_parameter(definition.scope_parameter),
|
|
42
|
+
label: definition.scope_label
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
private_class_method :scope_payload_for
|
|
46
|
+
|
|
47
|
+
def availability_for(definition, principal:)
|
|
48
|
+
return { enabled: true, reason: nil } unless admin_scope_registered?(definition.id)
|
|
49
|
+
|
|
50
|
+
registration = CommandTower.config.admin_scope.fetch(definition.id)
|
|
51
|
+
normalize_availability(registration.availability.call(principal:))
|
|
52
|
+
end
|
|
53
|
+
private_class_method :availability_for
|
|
54
|
+
|
|
55
|
+
def scope_options_for(definition, principal:)
|
|
56
|
+
return unless definition.scope_required? && admin_scope_registered?(definition.id)
|
|
57
|
+
|
|
58
|
+
registration = CommandTower.config.admin_scope.fetch(definition.id)
|
|
59
|
+
Array(registration.options.call(principal:)).map do |option|
|
|
60
|
+
normalize_scope_option(option)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
private_class_method :scope_options_for
|
|
64
|
+
|
|
65
|
+
def admin_scope_registered?(tool_id)
|
|
66
|
+
CommandTower.config.admin_scope.registered?(tool_id)
|
|
67
|
+
end
|
|
68
|
+
private_class_method :admin_scope_registered?
|
|
69
|
+
|
|
70
|
+
def normalize_availability(value)
|
|
71
|
+
hash = value.is_a?(Hash) ? value : {}
|
|
72
|
+
enabled = hash.fetch(:enabled, hash["enabled"])
|
|
73
|
+
reason = hash.fetch(:reason, hash["reason"])
|
|
74
|
+
{
|
|
75
|
+
enabled: enabled != false,
|
|
76
|
+
reason: reason.nil? ? nil : reason.to_s
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
private_class_method :normalize_availability
|
|
80
|
+
|
|
81
|
+
def normalize_scope_option(option)
|
|
82
|
+
case option
|
|
83
|
+
when AdminScope::ScopeOption
|
|
84
|
+
{ value: option.value, label: option.label }
|
|
85
|
+
when Hash
|
|
86
|
+
{
|
|
87
|
+
value: option.fetch(:value, option["value"]).to_s,
|
|
88
|
+
label: option.fetch(:label, option["label"]).to_s
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
raise ArgumentError, "scope option must be ScopeOption or Hash, got #{option.class}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
private_class_method :normalize_scope_option
|
|
95
|
+
|
|
96
|
+
def camelize_parameter(parameter)
|
|
97
|
+
parameter.to_s.camelize(:lower)
|
|
98
|
+
end
|
|
99
|
+
private_class_method :camelize_parameter
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module AdminScope
|
|
5
|
+
module Resolve
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call(tool_id:, principal:, scope_value:)
|
|
9
|
+
definition = CommandTower.config.registry.admin_workspace.fetch(tool_id)
|
|
10
|
+
return nil unless definition.scope_required?
|
|
11
|
+
|
|
12
|
+
token = scope_value.to_s.strip
|
|
13
|
+
if token.empty?
|
|
14
|
+
raise CommandTower::Errors::ForbiddenError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
registration = CommandTower.config.admin_scope.fetch(tool_id)
|
|
18
|
+
valid = registration.validate.call(value: token, principal:)
|
|
19
|
+
unless valid
|
|
20
|
+
raise CommandTower::Errors::ForbiddenError
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
ScopeContext.new(
|
|
24
|
+
tool_id: definition.id,
|
|
25
|
+
scope_value: token,
|
|
26
|
+
scope_parameter: definition.scope_parameter
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|