command_tower 0.3.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (603) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +104 -28
  3. data/app/auth/command_tower/auth/auth_context.rb +36 -0
  4. data/app/auth/command_tower/auth/password_recovery_session_context.rb +11 -0
  5. data/app/auth/command_tower/auth/signup_session_context.rb +11 -0
  6. data/app/controllers/command_tower/admin/messaging/announcements_controller.rb +34 -0
  7. data/app/controllers/command_tower/application_controller.rb +80 -14
  8. data/app/controllers/command_tower/auth/email/availability_controller.rb +35 -0
  9. data/app/controllers/command_tower/auth/email_verification/send_controller.rb +24 -0
  10. data/app/controllers/command_tower/auth/email_verification/verify_controller.rb +41 -0
  11. data/app/controllers/command_tower/auth/identity_policy_controller.rb +14 -0
  12. data/app/controllers/command_tower/auth/logout_controller.rb +14 -0
  13. data/app/controllers/command_tower/auth/password_recovery_session/create_controller.rb +17 -0
  14. data/app/controllers/command_tower/auth/password_reset/reset_controller.rb +35 -0
  15. data/app/controllers/command_tower/auth/password_reset/send_controller.rb +40 -0
  16. data/app/controllers/command_tower/auth/password_reset/validate_controller.rb +35 -0
  17. data/app/controllers/command_tower/auth/plain_text/login_controller.rb +22 -0
  18. data/app/controllers/command_tower/auth/register_controller.rb +33 -0
  19. data/app/controllers/command_tower/auth/session_controller.rb +21 -0
  20. data/app/controllers/command_tower/auth/signup_session/create_controller.rb +17 -0
  21. data/app/controllers/command_tower/auth/username/availability_controller.rb +35 -0
  22. data/app/controllers/command_tower/me/inbox_controller.rb +85 -0
  23. data/app/controllers/command_tower/me/name_controller.rb +37 -0
  24. data/app/controllers/command_tower/me/password_controller.rb +37 -0
  25. data/app/controllers/command_tower/me/phone_controller.rb +44 -0
  26. data/app/controllers/command_tower/me/phone_verification/verifications_controller.rb +23 -0
  27. data/app/controllers/command_tower/me/phone_verification/verify_controller.rb +34 -0
  28. data/app/controllers/command_tower/me/preferences_controller.rb +46 -0
  29. data/app/controllers/command_tower/me/pushover/verifications_controller.rb +23 -0
  30. data/app/controllers/command_tower/me/pushover_controller.rb +66 -0
  31. data/app/controllers/command_tower/me_controller.rb +19 -0
  32. data/app/controllers/command_tower/profile_controller.rb +19 -0
  33. data/app/controllers/concerns/command_tower/api/application_response_renderer.rb +82 -0
  34. data/app/controllers/concerns/command_tower/api/deserializer_failure_details.rb +22 -0
  35. data/app/controllers/concerns/command_tower/auth/authentication_boundary.rb +34 -0
  36. data/app/controllers/concerns/command_tower/auth/authorization_boundary.rb +26 -0
  37. data/app/controllers/concerns/command_tower/auth/invalid_credentials_deserializer_renderer.rb +23 -0
  38. data/app/controllers/concerns/command_tower/auth/password_recovery_session_boundary.rb +29 -0
  39. data/app/controllers/concerns/command_tower/auth/signup_session_boundary.rb +29 -0
  40. data/app/deserializers/command_tower/deserializers/admin/messaging/create_announcement_deserializer.rb +106 -0
  41. data/app/deserializers/command_tower/deserializers/application_deserializer.rb +148 -0
  42. data/app/deserializers/command_tower/deserializers/auth/email_availability_deserializer.rb +19 -0
  43. data/app/deserializers/command_tower/deserializers/auth/email_verification/verify_deserializer.rb +21 -0
  44. data/app/deserializers/command_tower/deserializers/auth/password_reset/reset_deserializer.rb +31 -0
  45. data/app/deserializers/command_tower/deserializers/auth/password_reset/send_deserializer.rb +21 -0
  46. data/app/deserializers/command_tower/deserializers/auth/password_reset/validate_deserializer.rb +23 -0
  47. data/app/deserializers/command_tower/deserializers/auth/plain_text/login_deserializer.rb +24 -0
  48. data/app/deserializers/command_tower/deserializers/auth/register_deserializer.rb +42 -0
  49. data/app/deserializers/command_tower/deserializers/auth/username_availability_deserializer.rb +19 -0
  50. data/app/deserializers/command_tower/deserializers/clients/errors.rb +45 -0
  51. data/app/deserializers/command_tower/deserializers/clients/missing.rb +10 -0
  52. data/app/deserializers/command_tower/deserializers/clients/payload.rb +21 -0
  53. data/app/deserializers/command_tower/deserializers/clients/result.rb +122 -0
  54. data/app/deserializers/command_tower/deserializers/clients/types.rb +192 -0
  55. data/app/deserializers/command_tower/deserializers/coercion_result.rb +35 -0
  56. data/app/deserializers/command_tower/deserializers/failure.rb +14 -0
  57. data/app/deserializers/command_tower/deserializers/me/change_password_deserializer.rb +41 -0
  58. data/app/deserializers/command_tower/deserializers/me/phone_verification/verify_deserializer.rb +29 -0
  59. data/app/deserializers/command_tower/deserializers/me/pushover/credentials_deserializer.rb +31 -0
  60. data/app/deserializers/command_tower/deserializers/me/update_name_deserializer.rb +22 -0
  61. data/app/deserializers/command_tower/deserializers/me/update_phone_deserializer.rb +22 -0
  62. data/app/deserializers/command_tower/deserializers/messaging/inbox.rb +77 -0
  63. data/app/deserializers/command_tower/deserializers/messaging/preferences/show_deserializer.rb +17 -0
  64. data/app/deserializers/command_tower/deserializers/messaging/preferences/update_deserializer.rb +97 -0
  65. data/app/errors/command_tower/errors/account/phone_already_verified_error.rb +17 -0
  66. data/app/errors/command_tower/errors/account/phone_missing_error.rb +17 -0
  67. data/app/errors/command_tower/errors/account/phone_verification_code_invalid_error.rb +17 -0
  68. data/app/errors/command_tower/errors/account/phone_verification_expired_error.rb +17 -0
  69. data/app/errors/command_tower/errors/account/phone_verification_send_failed_error.rb +25 -0
  70. data/app/errors/command_tower/errors/account/phone_verification_stale_error.rb +17 -0
  71. data/app/errors/command_tower/errors/account/phone_verification_throttled_error.rb +24 -0
  72. data/app/errors/command_tower/errors/account/pushover_already_configured_error.rb +21 -0
  73. data/app/errors/command_tower/errors/account/pushover_capability_unavailable_error.rb +21 -0
  74. data/app/errors/command_tower/errors/account/pushover_not_configured_error.rb +21 -0
  75. data/app/errors/command_tower/errors/account/pushover_provider_unavailable_error.rb +21 -0
  76. data/app/errors/command_tower/errors/account/pushover_verification_failed_error.rb +27 -0
  77. data/app/errors/command_tower/errors/account/sms_capability_unavailable_error.rb +21 -0
  78. data/app/errors/command_tower/errors/application_error.rb +27 -0
  79. data/app/errors/command_tower/errors/auth/csrf_mismatch_error.rb +17 -0
  80. data/app/errors/command_tower/errors/auth/csrf_missing_error.rb +17 -0
  81. data/app/errors/command_tower/errors/auth/email_already_registered_error.rb +17 -0
  82. data/app/errors/command_tower/errors/auth/email_verification_required_error.rb +21 -0
  83. data/app/errors/command_tower/errors/auth/invalid_credentials_error.rb +21 -0
  84. data/app/errors/command_tower/errors/auth/password_recovery_ip_rate_limit_error.rb +25 -0
  85. data/app/errors/command_tower/errors/auth/password_recovery_session_expired_error.rb +21 -0
  86. data/app/errors/command_tower/errors/auth/password_recovery_session_invalid_error.rb +21 -0
  87. data/app/errors/command_tower/errors/auth/password_recovery_session_missing_error.rb +21 -0
  88. data/app/errors/command_tower/errors/auth/password_recovery_session_rate_limit_error.rb +25 -0
  89. data/app/errors/command_tower/errors/auth/password_reset_invalid_token_error.rb +21 -0
  90. data/app/errors/command_tower/errors/auth/password_reset_unavailable_error.rb +21 -0
  91. data/app/errors/command_tower/errors/auth/signup_ip_rate_limit_error.rb +25 -0
  92. data/app/errors/command_tower/errors/auth/signup_session_expired_error.rb +17 -0
  93. data/app/errors/command_tower/errors/auth/signup_session_invalid_error.rb +17 -0
  94. data/app/errors/command_tower/errors/auth/signup_session_missing_error.rb +17 -0
  95. data/app/errors/command_tower/errors/auth/signup_session_rate_limit_error.rb +25 -0
  96. data/app/errors/command_tower/errors/auth/verification_code_invalid_error.rb +17 -0
  97. data/app/errors/command_tower/errors/auth/verification_send_failed_error.rb +25 -0
  98. data/app/errors/command_tower/errors/forbidden_error.rb +19 -0
  99. data/app/errors/command_tower/errors/internal_error.rb +19 -0
  100. data/app/errors/command_tower/errors/messaging/accept_rejected_error.rb +21 -0
  101. data/app/errors/command_tower/errors/messaging/idempotency_conflict_error.rb +21 -0
  102. data/app/errors/command_tower/errors/messaging/recipient_unresolved_error.rb +21 -0
  103. data/app/errors/command_tower/errors/not_found_error.rb +19 -0
  104. data/app/errors/command_tower/errors/unauthorized_error.rb +19 -0
  105. data/app/errors/command_tower/errors/validation_error.rb +15 -0
  106. data/app/jobs/command_tower/messaging/channel_delivery_execution_job.rb +18 -0
  107. data/app/jobs/command_tower/messaging/communications/produce_recipient_job.rb +24 -0
  108. data/app/jobs/command_tower/messaging/execution_recovery_job.rb +13 -0
  109. data/app/jobs/command_tower/messaging/handoff_job.rb +15 -0
  110. data/app/jobs/command_tower/messaging/handoff_recovery_job.rb +13 -0
  111. data/app/mailers/command_tower/email_verification_mailer.rb +5 -3
  112. data/app/mailers/command_tower/messaging/channel_mailer.rb +25 -0
  113. data/app/mailers/command_tower/password_reset_mailer.rb +18 -0
  114. data/app/models/command_tower/messaging/channel_delivery.rb +45 -0
  115. data/app/models/command_tower/messaging/communication.rb +60 -0
  116. data/app/models/command_tower/messaging/delivery_attempt.rb +28 -0
  117. data/app/models/command_tower/messaging/destination_plan.rb +17 -0
  118. data/app/models/command_tower/messaging/endpoint.rb +98 -0
  119. data/app/models/command_tower/messaging/endpoint_pushover_credential.rb +18 -0
  120. data/app/models/command_tower/messaging/inbox_item.rb +52 -0
  121. data/app/models/command_tower/messaging/notification_preference.rb +21 -0
  122. data/app/models/user.rb +4 -1
  123. data/app/serializers/command_tower/serializers/admin/messaging/announcement_response_serializer.rb +41 -0
  124. data/app/serializers/command_tower/serializers/application/envelope_serializer.rb +25 -0
  125. data/app/serializers/command_tower/serializers/application/error_entry_serializer.rb +18 -0
  126. data/app/serializers/command_tower/serializers/application_serializer.rb +26 -0
  127. data/app/serializers/command_tower/serializers/auth/email_availability_serializer.rb +17 -0
  128. data/app/serializers/command_tower/serializers/auth/email_verification/message_response_serializer.rb +15 -0
  129. data/app/serializers/command_tower/serializers/auth/identity_policy_serializer.rb +36 -0
  130. data/app/serializers/command_tower/serializers/auth/login_response_serializer.rb +17 -0
  131. data/app/serializers/command_tower/serializers/auth/logout_response_serializer.rb +13 -0
  132. data/app/serializers/command_tower/serializers/auth/password_recovery_session_response_serializer.rb +16 -0
  133. data/app/serializers/command_tower/serializers/auth/password_reset/message_response_serializer.rb +15 -0
  134. data/app/serializers/command_tower/serializers/auth/password_reset/validate_response_serializer.rb +22 -0
  135. data/app/serializers/command_tower/serializers/auth/register_response_serializer.rb +16 -0
  136. data/app/serializers/command_tower/serializers/auth/session_response_serializer.rb +16 -0
  137. data/app/serializers/command_tower/serializers/auth/signup_session_response_serializer.rb +16 -0
  138. data/app/serializers/command_tower/serializers/auth/user_serializer.rb +21 -0
  139. data/app/serializers/command_tower/serializers/auth/username_availability_serializer.rb +17 -0
  140. data/app/serializers/command_tower/serializers/me/account_serializer.rb +45 -0
  141. data/app/serializers/command_tower/serializers/me/change_password_response_serializer.rb +13 -0
  142. data/app/serializers/command_tower/serializers/me/pushover_serializer.rb +59 -0
  143. data/app/serializers/command_tower/serializers/messaging/inbox.rb +45 -0
  144. data/app/serializers/command_tower/serializers/messaging/preferences/catalog_serializer.rb +17 -0
  145. data/app/serializers/command_tower/serializers/messaging/preferences/category_serializer.rb +23 -0
  146. data/app/serializers/command_tower/serializers/messaging/preferences/notification_serializer.rb +30 -0
  147. data/app/serializers/command_tower/serializers/profile/profile_serializer.rb +13 -0
  148. data/app/services/command_tower/README.md +22 -6
  149. data/app/services/command_tower/auth/request_context.rb +21 -0
  150. data/app/services/command_tower/authorize/validate.rb +54 -18
  151. data/app/services/command_tower/clients/client_base.rb +183 -0
  152. data/app/services/command_tower/clients/client_result.rb +45 -0
  153. data/app/services/command_tower/clients/const_resolution.rb +32 -0
  154. data/app/services/command_tower/clients/decoded_response.rb +16 -0
  155. data/app/services/command_tower/clients/endpoint_base.rb +242 -0
  156. data/app/services/command_tower/clients/endpoint_input.rb +18 -0
  157. data/app/services/command_tower/clients/errors/authentication_error.rb +26 -0
  158. data/app/services/command_tower/clients/errors/configuration_error.rb +10 -0
  159. data/app/services/command_tower/clients/errors/deserialization_error.rb +26 -0
  160. data/app/services/command_tower/clients/errors/discovery_error.rb +10 -0
  161. data/app/services/command_tower/clients/errors/upstream_error.rb +26 -0
  162. data/app/services/command_tower/clients/invocation.rb +61 -0
  163. data/app/services/command_tower/clients/namespace_proxy.rb +75 -0
  164. data/app/services/command_tower/clients/scoped_client.rb +88 -0
  165. data/app/services/command_tower/clients/serialized_request.rb +17 -0
  166. data/app/services/command_tower/clients/transport/error.rb +18 -0
  167. data/app/services/command_tower/clients/transport/faraday_adapter.rb +129 -0
  168. data/app/services/command_tower/clients/transport/faraday_config.rb +45 -0
  169. data/app/services/command_tower/clients/transport/request.rb +50 -0
  170. data/app/services/command_tower/clients/transport/response.rb +22 -0
  171. data/app/services/command_tower/clients/url.rb +56 -0
  172. data/app/services/command_tower/identity/phone/normalizer.rb +41 -0
  173. data/app/services/command_tower/identity/phone_verification/sms_configuration.rb +58 -0
  174. data/app/services/command_tower/identity/phone_verification/sms_transport/adapters/fake_adapter.rb +35 -0
  175. data/app/services/command_tower/identity/phone_verification/sms_transport/adapters/log_adapter.rb +34 -0
  176. data/app/services/command_tower/identity/phone_verification/sms_transport/adapters/twilio_adapter.rb +60 -0
  177. data/app/services/command_tower/identity/phone_verification/sms_transport.rb +42 -0
  178. data/app/services/command_tower/jwt/authenticate_user.rb +61 -34
  179. data/app/services/command_tower/jwt/authentication_outcome.rb +36 -0
  180. data/app/services/command_tower/jwt/decode.rb +27 -8
  181. data/app/services/command_tower/jwt/encode.rb +4 -6
  182. data/app/services/command_tower/jwt/login_create.rb +6 -7
  183. data/app/services/command_tower/login_strategy/plain_text/email_verification/required.rb +11 -9
  184. data/app/services/command_tower/messaging/accept/channel_delivery_result.rb +13 -0
  185. data/app/services/command_tower/messaging/accept/coordinator.rb +166 -0
  186. data/app/services/command_tower/messaging/accept/error.rb +9 -0
  187. data/app/services/command_tower/messaging/accept/excluded_destination.rb +24 -0
  188. data/app/services/command_tower/messaging/accept/handoff_scheduler.rb +40 -0
  189. data/app/services/command_tower/messaging/accept/idempotency_conflict_error.rb +9 -0
  190. data/app/services/command_tower/messaging/accept/illegal_override_error.rb +9 -0
  191. data/app/services/command_tower/messaging/accept/impossible_mandatory_plan_error.rb +9 -0
  192. data/app/services/command_tower/messaging/accept/invalid_preference_error.rb +9 -0
  193. data/app/services/command_tower/messaging/accept/invariant_error.rb +9 -0
  194. data/app/services/command_tower/messaging/accept/operation_logger.rb +97 -0
  195. data/app/services/command_tower/messaging/accept/persistence_error.rb +9 -0
  196. data/app/services/command_tower/messaging/accept/persister.rb +90 -0
  197. data/app/services/command_tower/messaging/accept/request_normalizer.rb +100 -0
  198. data/app/services/command_tower/messaging/accept/result.rb +99 -0
  199. data/app/services/command_tower/messaging/accept/unknown_type_error.rb +9 -0
  200. data/app/services/command_tower/messaging/accept/validation_error.rb +9 -0
  201. data/app/services/command_tower/messaging/channel_detectors.rb +52 -0
  202. data/app/services/command_tower/messaging/channels/definition.rb +24 -0
  203. data/app/services/command_tower/messaging/channels/error.rb +9 -0
  204. data/app/services/command_tower/messaging/channels/unknown_channel_error.rb +9 -0
  205. data/app/services/command_tower/messaging/channels.rb +85 -0
  206. data/app/services/command_tower/messaging/contract/communications.rb +29 -0
  207. data/app/services/command_tower/messaging/contract/error.rb +9 -0
  208. data/app/services/command_tower/messaging/contract/internal/finder.rb +43 -0
  209. data/app/services/command_tower/messaging/contract/invariant_error.rb +9 -0
  210. data/app/services/command_tower/messaging/contract/mappers/channel_delivery_mapper.rb +53 -0
  211. data/app/services/command_tower/messaging/contract/mappers/communication_mapper.rb +61 -0
  212. data/app/services/command_tower/messaging/contract/mappers/delivery_attempt_mapper.rb +27 -0
  213. data/app/services/command_tower/messaging/contract/mappers/destination_plan_mapper.rb +17 -0
  214. data/app/services/command_tower/messaging/contract/mappers/inbox_item_mapper.rb +17 -0
  215. data/app/services/command_tower/messaging/contract/not_found_error.rb +9 -0
  216. data/app/services/command_tower/messaging/contract/observability/correlation.rb +17 -0
  217. data/app/services/command_tower/messaging/contract/observability/operation_logger.rb +101 -0
  218. data/app/services/command_tower/messaging/contract/observability/structured_logger.rb +56 -0
  219. data/app/services/command_tower/messaging/contract/requests/find_communication.rb +21 -0
  220. data/app/services/command_tower/messaging/contract/results/channel_delivery_result.rb +25 -0
  221. data/app/services/command_tower/messaging/contract/results/communication_result.rb +22 -0
  222. data/app/services/command_tower/messaging/contract/results/delivery_attempt_result.rb +21 -0
  223. data/app/services/command_tower/messaging/contract/results/destination_plan_result.rb +11 -0
  224. data/app/services/command_tower/messaging/contract/results/inbox_item_result.rb +11 -0
  225. data/app/services/command_tower/messaging/contract/validation_error.rb +9 -0
  226. data/app/services/command_tower/messaging/delivery_retry_policy.rb +30 -0
  227. data/app/services/command_tower/messaging/endpoints/begin_verification.rb +13 -0
  228. data/app/services/command_tower/messaging/endpoints/channel_gate.rb +39 -0
  229. data/app/services/command_tower/messaging/endpoints/conflict_error.rb +9 -0
  230. data/app/services/command_tower/messaging/endpoints/create.rb +181 -0
  231. data/app/services/command_tower/messaging/endpoints/error.rb +9 -0
  232. data/app/services/command_tower/messaging/endpoints/fingerprinter.rb +50 -0
  233. data/app/services/command_tower/messaging/endpoints/invalid_transition_error.rb +9 -0
  234. data/app/services/command_tower/messaging/endpoints/lifecycle.rb +37 -0
  235. data/app/services/command_tower/messaging/endpoints/mark_invalid.rb +32 -0
  236. data/app/services/command_tower/messaging/endpoints/mark_verification_failed.rb +13 -0
  237. data/app/services/command_tower/messaging/endpoints/mark_verified.rb +13 -0
  238. data/app/services/command_tower/messaging/endpoints/not_found_error.rb +9 -0
  239. data/app/services/command_tower/messaging/endpoints/persistence.rb +91 -0
  240. data/app/services/command_tower/messaging/endpoints/pushover/verify.rb +101 -0
  241. data/app/services/command_tower/messaging/endpoints/query.rb +28 -0
  242. data/app/services/command_tower/messaging/endpoints/replace.rb +181 -0
  243. data/app/services/command_tower/messaging/endpoints/reset_verification.rb +13 -0
  244. data/app/services/command_tower/messaging/endpoints/revoke.rb +40 -0
  245. data/app/services/command_tower/messaging/endpoints/safe_view.rb +50 -0
  246. data/app/services/command_tower/messaging/endpoints/secret_box.rb +86 -0
  247. data/app/services/command_tower/messaging/endpoints/secret_reader.rb +72 -0
  248. data/app/services/command_tower/messaging/endpoints/validation_error.rb +9 -0
  249. data/app/services/command_tower/messaging/endpoints/validators/push.rb +24 -0
  250. data/app/services/command_tower/messaging/endpoints/validators/pushover.rb +68 -0
  251. data/app/services/command_tower/messaging/endpoints/validators.rb +36 -0
  252. data/app/services/command_tower/messaging/endpoints/verification.rb +39 -0
  253. data/app/services/command_tower/messaging/endpoints/verification_failed_error.rb +18 -0
  254. data/app/services/command_tower/messaging/endpoints/verification_transition.rb +20 -0
  255. data/app/services/command_tower/messaging/endpoints.rb +55 -0
  256. data/app/services/command_tower/messaging/execution/adapter_request.rb +45 -0
  257. data/app/services/command_tower/messaging/execution/adapter_result.rb +81 -0
  258. data/app/services/command_tower/messaging/execution/adapters/email/adapter.rb +78 -0
  259. data/app/services/command_tower/messaging/execution/adapters/email/configuration.rb +55 -0
  260. data/app/services/command_tower/messaging/execution/adapters/fake_adapter.rb +34 -0
  261. data/app/services/command_tower/messaging/execution/adapters/pushover/adapter.rb +107 -0
  262. data/app/services/command_tower/messaging/execution/adapters/pushover/configuration.rb +30 -0
  263. data/app/services/command_tower/messaging/execution/adapters/sms/adapter.rb +90 -0
  264. data/app/services/command_tower/messaging/execution/adapters/sms/configuration.rb +58 -0
  265. data/app/services/command_tower/messaging/execution/adapters/sms/log_adapter.rb +34 -0
  266. data/app/services/command_tower/messaging/execution/adapters/sms/twilio_http_client.rb +70 -0
  267. data/app/services/command_tower/messaging/execution/adapters/unconfigured_adapter.rb +20 -0
  268. data/app/services/command_tower/messaging/execution/attempt_ledger.rb +146 -0
  269. data/app/services/command_tower/messaging/execution/claim.rb +35 -0
  270. data/app/services/command_tower/messaging/execution/invalid_adapter_contract_error.rb +9 -0
  271. data/app/services/command_tower/messaging/execution/operation_logger.rb +237 -0
  272. data/app/services/command_tower/messaging/execution/readiness_terminal_mapping.rb +37 -0
  273. data/app/services/command_tower/messaging/execution/recovery.rb +78 -0
  274. data/app/services/command_tower/messaging/execution/resolve_recipient_address.rb +58 -0
  275. data/app/services/command_tower/messaging/execution/revalidate_readiness.rb +73 -0
  276. data/app/services/command_tower/messaging/execution/scheduler.rb +38 -0
  277. data/app/services/command_tower/messaging/execution/select_adapter.rb +63 -0
  278. data/app/services/command_tower/messaging/handoff/advance_communication.rb +39 -0
  279. data/app/services/command_tower/messaging/handoff/operation_logger.rb +78 -0
  280. data/app/services/command_tower/messaging/handoff/recovery.rb +26 -0
  281. data/app/services/command_tower/messaging/inbox/bulk_result.rb +19 -0
  282. data/app/services/command_tower/messaging/inbox/error.rb +9 -0
  283. data/app/services/command_tower/messaging/inbox/invalid_bulk_items_error.rb +19 -0
  284. data/app/services/command_tower/messaging/inbox/invariant_error.rb +9 -0
  285. data/app/services/command_tower/messaging/inbox/item_result.rb +64 -0
  286. data/app/services/command_tower/messaging/inbox/list_result.rb +18 -0
  287. data/app/services/command_tower/messaging/inbox/mutator.rb +252 -0
  288. data/app/services/command_tower/messaging/inbox/not_found_error.rb +9 -0
  289. data/app/services/command_tower/messaging/inbox/operation_logger.rb +37 -0
  290. data/app/services/command_tower/messaging/inbox/reader.rb +117 -0
  291. data/app/services/command_tower/messaging/inbox/scope.rb +24 -0
  292. data/app/services/command_tower/messaging/inbox/unread_count_result.rb +16 -0
  293. data/app/services/command_tower/messaging/inbox/validation_error.rb +9 -0
  294. data/app/services/command_tower/messaging/inbox.rb +61 -0
  295. data/app/services/command_tower/messaging/notification_types/catalog_category.rb +19 -0
  296. data/app/services/command_tower/messaging/notification_types/declaration.rb +96 -0
  297. data/app/services/command_tower/messaging/notification_types/declaration_validator.rb +152 -0
  298. data/app/services/command_tower/messaging/notification_types/duplicate_type_error.rb +9 -0
  299. data/app/services/command_tower/messaging/notification_types/error.rb +9 -0
  300. data/app/services/command_tower/messaging/notification_types/invalid_declaration_error.rb +9 -0
  301. data/app/services/command_tower/messaging/notification_types/not_found_error.rb +9 -0
  302. data/app/services/command_tower/messaging/notification_types/registry.rb +103 -0
  303. data/app/services/command_tower/messaging/notification_types/sealed_registry_error.rb +9 -0
  304. data/app/services/command_tower/messaging/notification_types.rb +37 -0
  305. data/app/services/command_tower/messaging/planner/destination_plan.rb +41 -0
  306. data/app/services/command_tower/messaging/planner/error.rb +9 -0
  307. data/app/services/command_tower/messaging/planner/excluded_destination.rb +28 -0
  308. data/app/services/command_tower/messaging/planner/illegal_override_error.rb +9 -0
  309. data/app/services/command_tower/messaging/planner/impossible_mandatory_plan_error.rb +9 -0
  310. data/app/services/command_tower/messaging/planner/invalid_evaluation_error.rb +9 -0
  311. data/app/services/command_tower/messaging/planner/message_overrides.rb +33 -0
  312. data/app/services/command_tower/messaging/planner/operation_logger.rb +28 -0
  313. data/app/services/command_tower/messaging/planner/planner.rb +261 -0
  314. data/app/services/command_tower/messaging/planner/unknown_type_error.rb +9 -0
  315. data/app/services/command_tower/messaging/planner.rb +25 -0
  316. data/app/services/command_tower/messaging/preferences/catalog.rb +96 -0
  317. data/app/services/command_tower/messaging/preferences/catalog_category_result.rb +19 -0
  318. data/app/services/command_tower/messaging/preferences/catalog_notification_result.rb +55 -0
  319. data/app/services/command_tower/messaging/preferences/catalog_result.rb +13 -0
  320. data/app/services/command_tower/messaging/preferences/error.rb +9 -0
  321. data/app/services/command_tower/messaging/preferences/evaluation_result.rb +61 -0
  322. data/app/services/command_tower/messaging/preferences/evaluator.rb +194 -0
  323. data/app/services/command_tower/messaging/preferences/invalid_preference_state_error.rb +9 -0
  324. data/app/services/command_tower/messaging/preferences/invalid_preference_write_error.rb +10 -0
  325. data/app/services/command_tower/messaging/preferences/not_settings_visible_error.rb +10 -0
  326. data/app/services/command_tower/messaging/preferences/preference_state.rb +61 -0
  327. data/app/services/command_tower/messaging/preferences/reason_classes.rb +17 -0
  328. data/app/services/command_tower/messaging/preferences/resolve.rb +50 -0
  329. data/app/services/command_tower/messaging/preferences/store.rb +126 -0
  330. data/app/services/command_tower/messaging/preferences/store_error.rb +10 -0
  331. data/app/services/command_tower/messaging/preferences/suppressed_destination.rb +24 -0
  332. data/app/services/command_tower/messaging/preferences/unknown_type_error.rb +9 -0
  333. data/app/services/command_tower/messaging/preferences/update.rb +248 -0
  334. data/app/services/command_tower/messaging/preferences.rb +68 -0
  335. data/app/services/command_tower/messaging/pushover/adapters/disabled_adapter.rb +33 -0
  336. data/app/services/command_tower/messaging/pushover/adapters/fake_adapter.rb +90 -0
  337. data/app/services/command_tower/messaging/pushover/adapters/http_adapter.rb +145 -0
  338. data/app/services/command_tower/messaging/pushover/adapters/log_adapter.rb +52 -0
  339. data/app/services/command_tower/messaging/pushover/result.rb +34 -0
  340. data/app/services/command_tower/messaging/pushover/transport.rb +64 -0
  341. data/app/services/command_tower/messaging/recipient_readiness/channel_result.rb +55 -0
  342. data/app/services/command_tower/messaging/recipient_readiness/error.rb +9 -0
  343. data/app/services/command_tower/messaging/recipient_readiness/evaluate.rb +319 -0
  344. data/app/services/command_tower/messaging/recipient_readiness/identity_facts.rb +54 -0
  345. data/app/services/command_tower/messaging/recipient_readiness/reason_codes.rb +33 -0
  346. data/app/services/command_tower/messaging/recipient_readiness/recipient_not_found_error.rb +9 -0
  347. data/app/services/command_tower/messaging/recipient_readiness/recipient_result.rb +38 -0
  348. data/app/services/command_tower/messaging/recipient_readiness/unknown_channel_error.rb +9 -0
  349. data/app/services/command_tower/messaging/recipient_readiness.rb +26 -0
  350. data/app/services/command_tower/messaging/rendering/channel_renderer.rb +127 -0
  351. data/app/services/command_tower/messaging/rendering/render_error.rb +24 -0
  352. data/app/services/command_tower/messaging/rendering/rendered_payload.rb +44 -0
  353. data/app/services/command_tower/messaging/rendering/rendered_pushover_payload.rb +43 -0
  354. data/app/services/command_tower/messaging/rendering/rendered_sms_payload.rb +40 -0
  355. data/app/services/command_tower/messaging.rb +35 -0
  356. data/app/services/command_tower/secrets/cleanse.rb +19 -5
  357. data/app/services/command_tower/secrets/generate.rb +62 -27
  358. data/app/services/command_tower/secrets/verify.rb +46 -12
  359. data/app/services/command_tower/secrets.rb +2 -0
  360. data/app/services/command_tower/services/account/clear_phone.rb +25 -0
  361. data/app/services/command_tower/services/account/phone_verification/send.rb +114 -0
  362. data/app/services/command_tower/services/account/phone_verification/verify.rb +88 -0
  363. data/app/services/command_tower/services/account/pushover/create.rb +28 -0
  364. data/app/services/command_tower/services/account/pushover/ct_support.rb +70 -0
  365. data/app/services/command_tower/services/account/pushover/destroy.rb +30 -0
  366. data/app/services/command_tower/services/account/pushover/replace.rb +28 -0
  367. data/app/services/command_tower/services/account/pushover/show.rb +19 -0
  368. data/app/services/command_tower/services/account/pushover/verify.rb +30 -0
  369. data/app/services/command_tower/services/account/update_phone.rb +50 -0
  370. data/app/services/command_tower/services/application_service.rb +21 -0
  371. data/app/services/command_tower/services/auth/authenticate_session.rb +105 -0
  372. data/app/services/command_tower/services/auth/authorize_request.rb +34 -0
  373. data/app/services/command_tower/services/auth/client_ip_resolver.rb +13 -0
  374. data/app/services/command_tower/services/auth/email_availability.rb +42 -0
  375. data/app/services/command_tower/services/auth/email_verification/send.rb +65 -0
  376. data/app/services/command_tower/services/auth/email_verification/verify.rb +51 -0
  377. data/app/services/command_tower/services/auth/identity_policy.rb +60 -0
  378. data/app/services/command_tower/services/auth/password_recovery/rate_limits/check_send.rb +48 -0
  379. data/app/services/command_tower/services/auth/password_recovery/rate_limits/check_token_issue.rb +35 -0
  380. data/app/services/command_tower/services/auth/password_recovery/rate_limits/enforcement.rb +53 -0
  381. data/app/services/command_tower/services/auth/password_recovery_session/create.rb +26 -0
  382. data/app/services/command_tower/services/auth/password_recovery_session/decode.rb +29 -0
  383. data/app/services/command_tower/services/auth/password_recovery_session/encode.rb +31 -0
  384. data/app/services/command_tower/services/auth/password_recovery_session/validate.rb +58 -0
  385. data/app/services/command_tower/services/auth/password_reset/reset.rb +65 -0
  386. data/app/services/command_tower/services/auth/password_reset/send.rb +77 -0
  387. data/app/services/command_tower/services/auth/password_reset/token_verification.rb +68 -0
  388. data/app/services/command_tower/services/auth/password_reset/validate.rb +29 -0
  389. data/app/services/command_tower/services/auth/plain_text/login.rb +44 -0
  390. data/app/services/command_tower/services/auth/register.rb +77 -0
  391. data/app/services/command_tower/services/auth/signup_rate_limits/check_availability.rb +52 -0
  392. data/app/services/command_tower/services/auth/signup_rate_limits/check_register.rb +24 -0
  393. data/app/services/command_tower/services/auth/signup_rate_limits/check_token_issue.rb +33 -0
  394. data/app/services/command_tower/services/auth/signup_rate_limits/enforcement.rb +50 -0
  395. data/app/services/command_tower/services/auth/signup_session/create.rb +26 -0
  396. data/app/services/command_tower/services/auth/signup_session/decode.rb +29 -0
  397. data/app/services/command_tower/services/auth/signup_session/encode.rb +31 -0
  398. data/app/services/command_tower/services/auth/signup_session/validate.rb +58 -0
  399. data/app/services/command_tower/services/auth/username_availability.rb +28 -0
  400. data/app/services/command_tower/services/me/capabilities.rb +43 -0
  401. data/app/services/command_tower/services/me/change_password.rb +84 -0
  402. data/app/services/command_tower/services/me/pushover_product_gate.rb +18 -0
  403. data/app/services/command_tower/services/me/sms_product_gate.rb +19 -0
  404. data/app/services/command_tower/services/me/update_name.rb +58 -0
  405. data/app/services/command_tower/services/messaging/communications/produce.rb +68 -0
  406. data/app/services/command_tower/services/messaging/communications/produce_many.rb +154 -0
  407. data/app/services/command_tower/services/messaging/inbox.rb +129 -0
  408. data/app/services/command_tower/services/messaging/preferences/platform_enabled_channels.rb +20 -0
  409. data/app/services/command_tower/services/messaging/preferences/show.rb +36 -0
  410. data/app/services/command_tower/services/messaging/preferences/update.rb +43 -0
  411. data/app/services/command_tower/services/messaging/recipients.rb +20 -0
  412. data/app/services/command_tower/services/rate_limits/check.rb +31 -0
  413. data/app/services/command_tower/services/service_result.rb +73 -0
  414. data/app/services/command_tower/user_attributes/mutate.rb +154 -0
  415. data/app/services/command_tower/username/available.rb +24 -11
  416. data/app/views/command_tower/email_verification_mailer/verify_email.html.erb +70 -22
  417. data/app/views/command_tower/messaging/rendering/email.html.erb +28 -0
  418. data/app/views/command_tower/messaging/rendering/email.text.erb +7 -0
  419. data/app/views/command_tower/messaging/rendering/pushover.text.erb +1 -0
  420. data/app/views/command_tower/messaging/rendering/sms.text.erb +1 -0
  421. data/app/views/command_tower/password_reset_mailer/reset_password.html.erb +107 -0
  422. data/app/views/layouts/mailer.html.erb +13 -0
  423. data/app/views/layouts/mailer.text.erb +1 -0
  424. data/app/workflows/command_tower/workflows/admin/messaging/create_announcement_workflow.rb +62 -0
  425. data/app/workflows/command_tower/workflows/application_workflow.rb +68 -0
  426. data/app/workflows/command_tower/workflows/auth/authenticate_request_workflow.rb +40 -0
  427. data/app/workflows/command_tower/workflows/auth/authentication_response_effects.rb +21 -0
  428. data/app/workflows/command_tower/workflows/auth/authorize_request_workflow.rb +28 -0
  429. data/app/workflows/command_tower/workflows/auth/email_availability_workflow.rb +39 -0
  430. data/app/workflows/command_tower/workflows/auth/email_verification/send_workflow.rb +32 -0
  431. data/app/workflows/command_tower/workflows/auth/email_verification/verify_workflow.rb +35 -0
  432. data/app/workflows/command_tower/workflows/auth/identity_error_status.rb +36 -0
  433. data/app/workflows/command_tower/workflows/auth/identity_policy_workflow.rb +26 -0
  434. data/app/workflows/command_tower/workflows/auth/logout_workflow.rb +19 -0
  435. data/app/workflows/command_tower/workflows/auth/password_recovery_session/authenticate_workflow.rb +49 -0
  436. data/app/workflows/command_tower/workflows/auth/password_recovery_session/create_workflow.rb +39 -0
  437. data/app/workflows/command_tower/workflows/auth/password_reset/reset_workflow.rb +40 -0
  438. data/app/workflows/command_tower/workflows/auth/password_reset/send_workflow.rb +45 -0
  439. data/app/workflows/command_tower/workflows/auth/password_reset/validate_workflow.rb +40 -0
  440. data/app/workflows/command_tower/workflows/auth/plain_text/login_workflow.rb +46 -0
  441. data/app/workflows/command_tower/workflows/auth/register_workflow.rb +83 -0
  442. data/app/workflows/command_tower/workflows/auth/session/show_workflow.rb +44 -0
  443. data/app/workflows/command_tower/workflows/auth/session_error_status.rb +30 -0
  444. data/app/workflows/command_tower/workflows/auth/signup_error_status.rb +31 -0
  445. data/app/workflows/command_tower/workflows/auth/signup_session/authenticate_workflow.rb +49 -0
  446. data/app/workflows/command_tower/workflows/auth/signup_session/create_workflow.rb +37 -0
  447. data/app/workflows/command_tower/workflows/auth/username_availability_workflow.rb +39 -0
  448. data/app/workflows/command_tower/workflows/me/change_password_workflow.rb +34 -0
  449. data/app/workflows/command_tower/workflows/me/clear_phone_workflow.rb +52 -0
  450. data/app/workflows/command_tower/workflows/me/error_mapping.rb +46 -0
  451. data/app/workflows/command_tower/workflows/me/error_status.rb +28 -0
  452. data/app/workflows/command_tower/workflows/me/phone_verification/send_workflow.rb +65 -0
  453. data/app/workflows/command_tower/workflows/me/phone_verification/verify_workflow.rb +57 -0
  454. data/app/workflows/command_tower/workflows/me/pushover/create_workflow.rb +38 -0
  455. data/app/workflows/command_tower/workflows/me/pushover/destroy_workflow.rb +34 -0
  456. data/app/workflows/command_tower/workflows/me/pushover/replace_workflow.rb +38 -0
  457. data/app/workflows/command_tower/workflows/me/pushover/show_workflow.rb +34 -0
  458. data/app/workflows/command_tower/workflows/me/pushover/verify_workflow.rb +34 -0
  459. data/app/workflows/command_tower/workflows/me/pushover/workflow_support.rb +31 -0
  460. data/app/workflows/command_tower/workflows/me/show_workflow.rb +33 -0
  461. data/app/workflows/command_tower/workflows/me/update_name_workflow.rb +48 -0
  462. data/app/workflows/command_tower/workflows/me/update_phone_workflow.rb +55 -0
  463. data/app/workflows/command_tower/workflows/messaging/communications/produce_recipient_workflow.rb +60 -0
  464. data/app/workflows/command_tower/workflows/messaging/error_mapping.rb +26 -0
  465. data/app/workflows/command_tower/workflows/messaging/execution/deliver_workflow.rb +234 -0
  466. data/app/workflows/command_tower/workflows/messaging/handoff/process_workflow.rb +90 -0
  467. data/app/workflows/command_tower/workflows/messaging/inbox.rb +97 -0
  468. data/app/workflows/command_tower/workflows/messaging/preferences/show_workflow.rb +31 -0
  469. data/app/workflows/command_tower/workflows/messaging/preferences/update_workflow.rb +37 -0
  470. data/app/workflows/command_tower/workflows/profile/show_workflow.rb +27 -0
  471. data/app/workflows/command_tower/workflows/workflow_result.rb +34 -0
  472. data/config/routes.rb +85 -37
  473. data/db/migrate/{20241117043720_create_command_tower_users.rb → 20260805000001_create_command_tower_users.rb} +7 -9
  474. data/db/migrate/{20241204065708_create_command_tower_user_secrets.rb → 20260805000002_create_command_tower_user_secrets.rb} +3 -0
  475. data/db/migrate/20260805000003_create_command_tower_messaging_core.rb +90 -0
  476. data/db/migrate/20260805000004_create_messaging_notification_preferences.rb +17 -0
  477. data/db/migrate/20260805000005_create_messaging_endpoints_and_pushover_credentials.rb +72 -0
  478. data/docs/api_reference.md +438 -0
  479. data/docs/architecture.md +30 -0
  480. data/docs/authentication.md +37 -0
  481. data/docs/authentication_authorization_guide.md +150 -0
  482. data/docs/authorization.md +34 -0
  483. data/docs/change_password_workflow.md +67 -0
  484. data/docs/controllers.md +35 -0
  485. data/docs/cookie_authentication_guide.md +167 -0
  486. data/docs/email_verification_workflow.md +58 -0
  487. data/docs/extending.md +105 -0
  488. data/docs/host_integration_guide.md +158 -0
  489. data/docs/initializing.md +169 -0
  490. data/docs/messaging_integration_guide.md +87 -0
  491. data/docs/models.md +51 -0
  492. data/docs/pagination.md +47 -0
  493. data/docs/password_reset_workflow.md +82 -0
  494. data/docs/sensitive_routes.md +47 -0
  495. data/docs/testing.md +74 -0
  496. data/docs/upgrades/0.10.0.md +58 -0
  497. data/docs/upgrades/README.md +9 -0
  498. data/lib/command_tower/authorization/default.yml +5 -33
  499. data/lib/command_tower/configuration/application/config.rb +2 -2
  500. data/lib/command_tower/configuration/base.rb +0 -1
  501. data/lib/command_tower/configuration/config.rb +41 -5
  502. data/lib/command_tower/configuration/credentials/config.rb +33 -0
  503. data/lib/command_tower/configuration/credentials/smtp.rb +33 -0
  504. data/lib/command_tower/configuration/credentials/twilio.rb +33 -0
  505. data/lib/command_tower/configuration/email/config.rb +1 -18
  506. data/lib/command_tower/configuration/identity/config.rb +19 -0
  507. data/lib/command_tower/configuration/identity/phone_verification.rb +54 -0
  508. data/lib/command_tower/configuration/jwt/config.rb +7 -0
  509. data/lib/command_tower/configuration/jwt/cookie/config.rb +64 -0
  510. data/lib/command_tower/configuration/jwt/cookie/csrf/config.rb +72 -0
  511. data/lib/command_tower/configuration/login/strategy/plain_text/config.rb +6 -0
  512. data/lib/command_tower/configuration/login/strategy/plain_text/email_verify.rb +5 -0
  513. data/lib/command_tower/configuration/login/strategy/plain_text/password_reset.rb +49 -0
  514. data/lib/command_tower/configuration/messaging/config.rb +60 -0
  515. data/lib/command_tower/configuration/messaging/pushover.rb +42 -0
  516. data/lib/command_tower/configuration/messaging/sms.rb +32 -0
  517. data/lib/command_tower/configuration/pagination/config.rb +16 -0
  518. data/lib/command_tower/configuration/password_recovery_session/config.rb +61 -0
  519. data/lib/command_tower/configuration/password_recovery_session/rate_limits.rb +33 -0
  520. data/lib/command_tower/configuration/signup_session/config.rb +67 -0
  521. data/lib/command_tower/configuration/signup_session/email_availability.rb +18 -0
  522. data/lib/command_tower/configuration/signup_session/rate_limits.rb +48 -0
  523. data/lib/command_tower/configuration/user/config.rb +6 -5
  524. data/lib/command_tower/credential_resolution/env_backend.rb +25 -0
  525. data/lib/command_tower/credential_resolution/smtp_action_mailer_bridge.rb +24 -0
  526. data/lib/command_tower/credential_resolution/smtp_credentials.rb +26 -0
  527. data/lib/command_tower/credential_resolution/twilio_credentials.rb +26 -0
  528. data/lib/command_tower/credential_resolution.rb +78 -0
  529. data/lib/command_tower/current.rb +7 -0
  530. data/lib/command_tower/engine.rb +4 -1
  531. data/lib/command_tower/install/baseline.rb +31 -0
  532. data/lib/command_tower/install/doctor.rb +227 -0
  533. data/lib/command_tower/jwt/authorization_helper.rb +136 -0
  534. data/lib/command_tower/jwt/csrf_helper.rb +138 -0
  535. data/lib/command_tower/password_recovery/authorization_helper.rb +25 -0
  536. data/lib/command_tower/redis_connection.rb +21 -0
  537. data/lib/command_tower/schema/error/email_validation_required.rb +15 -0
  538. data/lib/command_tower/schema/shared/user.rb +32 -0
  539. data/lib/command_tower/schema.rb +2 -25
  540. data/lib/command_tower/signup/authorization_helper.rb +25 -0
  541. data/lib/command_tower/spec_helper.rb +58 -1
  542. data/lib/command_tower/testing.rb +62 -0
  543. data/lib/command_tower/version.rb +1 -1
  544. data/lib/command_tower.rb +20 -0
  545. data/lib/generators/command_tower/configure/USAGE +20 -0
  546. data/lib/generators/command_tower/configure/configure_generator.rb +39 -0
  547. data/lib/tasks/auto_annotate_models.rake +23 -55
  548. data/lib/tasks/install.rake +81 -0
  549. data/lib/tasks/users.rake +119 -0
  550. data/spec/factories/entity.rb +37 -0
  551. data/spec/factories/messaging.rb +156 -0
  552. data/spec/factories/role.rb +29 -0
  553. data/spec/factories/user.rb +53 -0
  554. data/spec/factories/user_secret.rb +12 -0
  555. metadata +579 -53
  556. data/app/controllers/command_tower/admin_controller.rb +0 -104
  557. data/app/controllers/command_tower/auth/plain_text_controller.rb +0 -132
  558. data/app/controllers/command_tower/inbox/message_blast_controller.rb +0 -89
  559. data/app/controllers/command_tower/inbox/message_controller.rb +0 -79
  560. data/app/controllers/command_tower/user_controller.rb +0 -49
  561. data/app/controllers/command_tower/username_controller.rb +0 -26
  562. data/app/helpers/command_tower/schema_helper.rb +0 -29
  563. data/app/models/message.rb +0 -30
  564. data/app/models/message_blast.rb +0 -27
  565. data/app/services/command_tower/inbox_service/blast/delete.rb +0 -23
  566. data/app/services/command_tower/inbox_service/blast/metadata.rb +0 -26
  567. data/app/services/command_tower/inbox_service/blast/new_user_blaster.rb +0 -24
  568. data/app/services/command_tower/inbox_service/blast/retrieve.rb +0 -30
  569. data/app/services/command_tower/inbox_service/blast/upsert.rb +0 -67
  570. data/app/services/command_tower/inbox_service/message/metadata.rb +0 -35
  571. data/app/services/command_tower/inbox_service/message/modify.rb +0 -44
  572. data/app/services/command_tower/inbox_service/message/retrieve.rb +0 -36
  573. data/app/services/command_tower/inbox_service/message/send.rb +0 -33
  574. data/app/services/command_tower/jwt/time_delay_token.rb +0 -17
  575. data/app/services/command_tower/login_strategy/plain_text/create.rb +0 -43
  576. data/app/services/command_tower/login_strategy/plain_text/email_verification/generate.rb +0 -29
  577. data/app/services/command_tower/login_strategy/plain_text/email_verification/send.rb +0 -23
  578. data/app/services/command_tower/login_strategy/plain_text/email_verification/verify.rb +0 -24
  579. data/app/services/command_tower/login_strategy/plain_text/login.rb +0 -50
  580. data/app/services/command_tower/user_attributes/modify.rb +0 -68
  581. data/app/services/command_tower/user_attributes/roles.rb +0 -27
  582. data/db/migrate/20250223023306_create_command_tower_messages.rb +0 -12
  583. data/db/migrate/20250223023313_create_command_tower_message_blasts.rb +0 -14
  584. data/lib/command_tower/schema/admin/users.rb +0 -15
  585. data/lib/command_tower/schema/inbox/blast_request.rb +0 -15
  586. data/lib/command_tower/schema/inbox/blast_response.rb +0 -16
  587. data/lib/command_tower/schema/inbox/message_blast_entity.rb +0 -16
  588. data/lib/command_tower/schema/inbox/message_blast_metadata.rb +0 -16
  589. data/lib/command_tower/schema/inbox/message_entity.rb +0 -14
  590. data/lib/command_tower/schema/inbox/metadata.rb +0 -18
  591. data/lib/command_tower/schema/inbox/modified.rb +0 -13
  592. data/lib/command_tower/schema/page.rb +0 -14
  593. data/lib/command_tower/schema/plain_text/create_user_request.rb +0 -18
  594. data/lib/command_tower/schema/plain_text/create_user_response.rb +0 -17
  595. data/lib/command_tower/schema/plain_text/email_verify_request.rb +0 -11
  596. data/lib/command_tower/schema/plain_text/email_verify_response.rb +0 -11
  597. data/lib/command_tower/schema/plain_text/email_verify_send_request.rb +0 -9
  598. data/lib/command_tower/schema/plain_text/email_verify_send_response.rb +0 -11
  599. data/lib/command_tower/schema/plain_text/login_request.rb +0 -15
  600. data/lib/command_tower/schema/plain_text/login_response.rb +0 -13
  601. data/lib/command_tower/schema/user.rb +0 -28
  602. data/lib/generators/api_engine_base/configure/USAGE +0 -8
  603. data/lib/generators/api_engine_base/configure/configure_generator.rb +0 -12
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "command_tower/install/baseline"
4
+ require "command_tower/configuration/messaging/sms"
5
+ require "command_tower/configuration/messaging/pushover"
6
+
7
+ module CommandTower
8
+ module Install
9
+ # Friendly setup validation for host applications.
10
+ # Returns structured findings; rake task prints remediation text.
11
+ class Doctor
12
+ INSECURE_JWT_DEFAULT = "Thi$IsASeccretIwi::CH&ang3"
13
+ SUPPORTED_SMS_ADAPTERS = Configuration::Messaging::Sms::ADAPTERS
14
+ SUPPORTED_PUSHOVER_ADAPTERS = Configuration::Messaging::Pushover::ADAPTERS
15
+ Finding = Struct.new(:severity, :code, :message, :remediation, keyword_init: true)
16
+
17
+ def initialize(host_root: Rails.root, config: CommandTower.config, env: Rails.env)
18
+ @host_root = Pathname(host_root)
19
+ @config = config
20
+ @env = env.to_s
21
+ end
22
+
23
+ def run
24
+ findings = []
25
+ findings.concat(check_rails_version)
26
+ findings.concat(check_engine_migrations)
27
+ findings.concat(check_host_migrations)
28
+ findings.concat(check_jwt_secret)
29
+ findings.concat(check_session_secrets)
30
+ findings.concat(check_messaging_adapters)
31
+ findings
32
+ end
33
+
34
+ def ok?
35
+ run.none? { |f| f.severity == :fail }
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :host_root, :config, :env
41
+
42
+ def check_rails_version
43
+ version = Gem::Version.new(Rails::VERSION::STRING)
44
+ min = Gem::Version.new("7.0.0")
45
+ max = Gem::Version.new("9.0.0")
46
+ if version >= min && version < max
47
+ [Finding.new(
48
+ severity: :pass,
49
+ code: :rails_version,
50
+ message: "Rails #{Rails::VERSION::STRING} is supported",
51
+ remediation: nil
52
+ )]
53
+ else
54
+ [Finding.new(
55
+ severity: :fail,
56
+ code: :rails_version,
57
+ message: "Rails #{Rails::VERSION::STRING} is outside CommandTower's supported range",
58
+ remediation: "Use Rails >= 7.0 and < 9.0 (see command_tower.gemspec)."
59
+ )]
60
+ end
61
+ end
62
+
63
+ def check_engine_migrations
64
+ actual = Baseline.engine_migration_basenames
65
+ expected = Baseline::ENGINE_MIGRATION_BASENAMES
66
+ if actual == expected
67
+ [Finding.new(
68
+ severity: :pass,
69
+ code: :engine_migrations,
70
+ message: "Engine ships the #{expected.size}-migration baseline",
71
+ remediation: nil
72
+ )]
73
+ else
74
+ [Finding.new(
75
+ severity: :fail,
76
+ code: :engine_migrations,
77
+ message: "Engine migration inventory does not match the expected baseline",
78
+ remediation: "Expected #{expected.join(', ')}; found #{actual.join(', ') || '(none)'}."
79
+ )]
80
+ end
81
+ end
82
+
83
+ def check_host_migrations
84
+ installed = Baseline.host_installed_migrations(host_root)
85
+ expected_count = Baseline::ENGINE_MIGRATION_BASENAMES.size
86
+
87
+ if installed.empty?
88
+ [Finding.new(
89
+ severity: :fail,
90
+ code: :host_migrations,
91
+ message: "No CommandTower migrations installed in the host",
92
+ remediation: "Run `bin/rails command_tower:install` (or `command_tower:install:migrations`), then `bin/rails db:migrate`."
93
+ )]
94
+ elsif installed.size < expected_count
95
+ [Finding.new(
96
+ severity: :fail,
97
+ code: :host_migrations,
98
+ message: "Host has #{installed.size} CommandTower migration(s); expected at least #{expected_count}",
99
+ remediation: "Run `bin/rails command_tower:install:migrations` after upgrading the gem, then `bin/rails db:migrate`."
100
+ )]
101
+ else
102
+ [Finding.new(
103
+ severity: :pass,
104
+ code: :host_migrations,
105
+ message: "Host has #{installed.size} installed CommandTower migration(s)",
106
+ remediation: nil
107
+ )]
108
+ end
109
+ end
110
+
111
+ def check_jwt_secret
112
+ secret = config.jwt.hmac_secret.to_s
113
+ if secret.blank?
114
+ return [Finding.new(
115
+ severity: :fail,
116
+ code: :jwt_secret,
117
+ message: "JWT HMAC secret is blank",
118
+ remediation: "Set `config.jwt.hmac_secret` (typically `ENV.fetch(\"SECRET_KEY_BASE\") { Rails.application.secret_key_base }`)."
119
+ )]
120
+ end
121
+
122
+ if secret == INSECURE_JWT_DEFAULT
123
+ severity = production? ? :fail : :warn
124
+ return [Finding.new(
125
+ severity: severity,
126
+ code: :jwt_secret,
127
+ message: "JWT HMAC secret is still the insecure CommandTower default",
128
+ remediation: "Set `config.jwt.hmac_secret` from SECRET_KEY_BASE or Rails.application.secret_key_base before deploying."
129
+ )]
130
+ end
131
+
132
+ [Finding.new(
133
+ severity: :pass,
134
+ code: :jwt_secret,
135
+ message: "JWT HMAC secret is configured",
136
+ remediation: nil
137
+ )]
138
+ end
139
+
140
+ def check_session_secrets
141
+ findings = []
142
+
143
+ unless production_or_development_missing_ok?(config.signup_session.jwt_secret)
144
+ findings << Finding.new(
145
+ severity: production? ? :warn : :warn,
146
+ code: :signup_session_secret,
147
+ message: "Signup session JWT secret is blank",
148
+ remediation: "Set `config.signup_session.jwt_secret` or ENV SIGNUP_SESSION_JWT_SECRET."
149
+ )
150
+ else
151
+ findings << Finding.new(
152
+ severity: :pass,
153
+ code: :signup_session_secret,
154
+ message: "Signup session JWT secret is configured",
155
+ remediation: nil
156
+ )
157
+ end
158
+
159
+ unless production_or_development_missing_ok?(config.password_recovery_session.jwt_secret)
160
+ findings << Finding.new(
161
+ severity: :warn,
162
+ code: :password_recovery_secret,
163
+ message: "Password recovery session JWT secret is blank",
164
+ remediation: "Set `config.password_recovery_session.jwt_secret` or ENV PASSWORD_RECOVERY_SESSION_JWT_SECRET."
165
+ )
166
+ else
167
+ findings << Finding.new(
168
+ severity: :pass,
169
+ code: :password_recovery_secret,
170
+ message: "Password recovery session JWT secret is configured",
171
+ remediation: nil
172
+ )
173
+ end
174
+
175
+ findings
176
+ end
177
+
178
+ def check_messaging_adapters
179
+ findings = []
180
+ sms = config.messaging.sms.adapter.to_s
181
+ pushover = config.messaging.pushover.adapter.to_s
182
+
183
+ if sms.present? && !SUPPORTED_SMS_ADAPTERS.include?(sms)
184
+ findings << Finding.new(
185
+ severity: :warn,
186
+ code: :sms_adapter,
187
+ message: "Unsupported messaging SMS adapter #{sms.inspect}",
188
+ remediation: "Use one of: #{SUPPORTED_SMS_ADAPTERS.join(', ')}."
189
+ )
190
+ else
191
+ findings << Finding.new(
192
+ severity: :pass,
193
+ code: :sms_adapter,
194
+ message: "Messaging SMS adapter is #{sms.presence || 'blank (defaults apply)'}",
195
+ remediation: nil
196
+ )
197
+ end
198
+
199
+ if pushover.present? && !SUPPORTED_PUSHOVER_ADAPTERS.include?(pushover)
200
+ findings << Finding.new(
201
+ severity: :warn,
202
+ code: :pushover_adapter,
203
+ message: "Unsupported messaging Pushover adapter #{pushover.inspect}",
204
+ remediation: "Use one of: #{SUPPORTED_PUSHOVER_ADAPTERS.join(', ')}."
205
+ )
206
+ else
207
+ findings << Finding.new(
208
+ severity: :pass,
209
+ code: :pushover_adapter,
210
+ message: "Messaging Pushover adapter is #{pushover.presence || 'blank (defaults apply)'}",
211
+ remediation: nil
212
+ )
213
+ end
214
+
215
+ findings
216
+ end
217
+
218
+ def production_or_development_missing_ok?(secret)
219
+ secret.to_s.present? || env == "test"
220
+ end
221
+
222
+ def production?
223
+ env == "production"
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Jwt
5
+ module AuthorizationHelper
6
+ AUTHENTICATION_HEADER = "Authorization"
7
+ AUTHENTICATION_EXPIRE_HEADER = "X-Authorization-Expire"
8
+ AUTHENTICATION_WITH_RESET = "X-Authorization-Reset"
9
+
10
+ module_function
11
+
12
+ # Validates and extracts token from Authorization header OR cookie (if enabled)
13
+ # Returns hash with :token and :source on success, or hash with :error and :message on failure
14
+ # @param request [ActionDispatch::Request] The request object
15
+ # @return [Hash] Hash with :token and :source (:header, :cookie) on success, or :error and :message on failure
16
+ def extract_token(request)
17
+ # First try Authorization header
18
+ raw_token = request.headers[AUTHENTICATION_HEADER]
19
+ if raw_token && !raw_token.to_s.strip.empty?
20
+ # Validate Bearer format with strict regex (case-insensitive)
21
+ # Matches: \ABearer\s+(.+)\z
22
+ # - \A: start of string
23
+ # - Bearer: literal "Bearer" (case-insensitive with /i flag)
24
+ # - \s+: one or more whitespace characters
25
+ # - (.+): capture group for token (one or more characters)
26
+ # - \z: end of string
27
+ match = raw_token.match(/\ABearer\s+(.+)\z/i)
28
+ if match && match[1] && !match[1].strip.empty?
29
+ token = match[1].strip
30
+ return { token:, source: :header }
31
+ else
32
+ return { error: :invalid_format, message: "Invalid Bearer token format" }
33
+ end
34
+ end
35
+
36
+ # Fallback to cookie if header is missing/empty AND cookie enabled
37
+ if cookie_enabled?
38
+ cookie_token = read_cookie(request)
39
+ return { token: cookie_token, source: :cookie } if cookie_token && !cookie_token.strip.empty?
40
+ end
41
+
42
+ # No token found
43
+ { error: :missing, message: "Bearer token missing" }
44
+ end
45
+
46
+ # Sets token in response (headers and optionally cookie)
47
+ # @param response [ActionDispatch::Response] The response object
48
+ # @param token [String] The JWT token to set
49
+ # @param expires_at [String, nil] Optional expiration time string
50
+ def set_token(response, token, expires_at: nil)
51
+ # Always set headers
52
+ response.set_header(AUTHENTICATION_WITH_RESET, token)
53
+ response.set_header(AUTHENTICATION_EXPIRE_HEADER, expires_at) if expires_at && !expires_at.to_s.strip.empty?
54
+
55
+ # Set cookie if enabled
56
+ set_cookie(response, token) if cookie_enabled?
57
+ end
58
+
59
+ # Clears token from response (cookie only, headers are response-only)
60
+ # @param response [ActionDispatch::Response] The response object
61
+ def clear_token(response)
62
+ clear_cookie(response) if cookie_enabled?
63
+ # Clear CSRF cookie on logout
64
+ # Logout always clears CSRF cookie (not configurable - this is the only correct behavior)
65
+ CommandTower::Jwt::CsrfHelper.clear_cookie(response) if CommandTower::Jwt::CsrfHelper.csrf_enabled?
66
+ end
67
+
68
+ # Reads cookie value from request
69
+ # @param request [ActionDispatch::Request] The request object
70
+ # @return [String, nil] Cookie value or nil
71
+ def read_cookie(request)
72
+ return nil unless cookie_enabled?
73
+
74
+ request.cookies[CommandTower.config.jwt.cookie.name.downcase]
75
+ end
76
+
77
+ # Returns consistent cookie options hash (single source of truth)
78
+ # @param expires_at [Time, ActiveSupport::TimeWithZone] Expiration time for the cookie
79
+ # @return [Hash] Cookie options hash with all attributes
80
+ def cookie_options(expires_at:)
81
+ config = CommandTower.config.jwt.cookie
82
+ secure_value = config.secure || Rails.env.production?
83
+ options = {
84
+ expires: expires_at,
85
+ httponly: config.httponly,
86
+ secure: secure_value,
87
+ same_site: config.same_site,
88
+ path: config.path
89
+ }
90
+ options[:domain] = config.domain if config.domain.present?
91
+ options
92
+ end
93
+
94
+ # Sets cookie with configured options
95
+ # @param response [ActionDispatch::Response] The response object
96
+ # @param token [String] The JWT token to set
97
+ def set_cookie(response, token)
98
+ return unless cookie_enabled?
99
+
100
+ config = CommandTower.config.jwt.cookie
101
+ options = cookie_options(expires_at: config.ttl.from_now)
102
+ options[:value] = token
103
+
104
+ response.set_cookie(config.name, options)
105
+ end
106
+
107
+ # Clears cookie by setting it to expire in the past
108
+ # @param response [ActionDispatch::Response] The response object
109
+ def clear_cookie(response)
110
+ return unless cookie_enabled?
111
+
112
+ config = CommandTower.config.jwt.cookie
113
+ options = cookie_options(expires_at: 1.year.ago)
114
+ options[:value] = ""
115
+
116
+ response.set_cookie(config.name, options)
117
+ end
118
+
119
+ # Gets the with_reset flag from request headers
120
+ # @param request [ActionDispatch::Request] The request object
121
+ # @return [Boolean, nil] The boolean value or nil
122
+ def get_with_reset_flag(request)
123
+ value = request.headers[AUTHENTICATION_WITH_RESET]
124
+ return nil unless [true, false, "true", "false", "0", "1", 0, 1].include?(value)
125
+
126
+ ActiveModel::Type::Boolean.new.cast(value)
127
+ end
128
+
129
+ # Checks if cookie auth is enabled
130
+ # @return [Boolean]
131
+ def cookie_enabled?
132
+ CommandTower.config.jwt.cookie.enabled?
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/security_utils"
4
+
5
+ module CommandTower
6
+ module Jwt
7
+ module CsrfHelper
8
+ module_function
9
+
10
+ # Generate a new CSRF token
11
+ def generate_token
12
+ SecureRandom.hex(32)
13
+ end
14
+
15
+ # Set CSRF cookie in response
16
+ def set_cookie(response, token)
17
+ return unless csrf_enabled?
18
+
19
+ config = CommandTower.config.jwt.cookie.csrf
20
+ options = csrf_cookie_options(expires_at: config.ttl.from_now)
21
+ options[:value] = token
22
+
23
+ response.set_cookie(config.cookie_name, options)
24
+ end
25
+
26
+ # Clear CSRF cookie
27
+ def clear_cookie(response)
28
+ return unless csrf_enabled?
29
+
30
+ config = CommandTower.config.jwt.cookie.csrf
31
+ options = csrf_cookie_options(expires_at: 1.year.ago)
32
+ options[:value] = ""
33
+ options[:max_age] = 0
34
+
35
+ response.set_cookie(config.cookie_name, options)
36
+ end
37
+
38
+ # Ensure CSRF cookie exists (rotate-or-ensure model)
39
+ # - If should_rotate is true → generate + set new CSRF cookie
40
+ # - Else if existing cookie is blank → generate + set new CSRF cookie
41
+ # - Else do nothing (cookie already exists)
42
+ def ensure_cookie(request, response, should_rotate: false)
43
+ return unless csrf_enabled?
44
+
45
+ # Read cookie once into local variable
46
+ existing_cookie = read_cookie(request)
47
+ existing_cookie_blank = existing_cookie.nil? || existing_cookie.strip.empty?
48
+
49
+ if should_rotate || existing_cookie_blank
50
+ # Generate and set new CSRF cookie
51
+ token = generate_token
52
+ set_cookie(response, token)
53
+ end
54
+ # Else: cookie exists and we're not rotating, do nothing
55
+ end
56
+
57
+ # Read CSRF token from cookie
58
+ def read_cookie(request)
59
+ return nil unless csrf_enabled?
60
+
61
+ config = CommandTower.config.jwt.cookie.csrf
62
+ request.cookies[config.cookie_name]
63
+ end
64
+
65
+ # Read CSRF token from header
66
+ def read_header(request)
67
+ return nil unless csrf_enabled?
68
+
69
+ config = CommandTower.config.jwt.cookie.csrf
70
+ request.headers[config.header_name]
71
+ end
72
+
73
+ # Validate CSRF token (compare cookie to header)
74
+ # Uses constant-time comparison for security hardening
75
+ def validate(request)
76
+ return { valid: true } unless csrf_enabled?
77
+
78
+ # read_cookie and read_header also check csrf_enabled?, but that's fine for redundancy
79
+ cookie_token = read_cookie(request)
80
+ header_token = read_header(request)
81
+
82
+ if cookie_token.nil? || cookie_token.strip.empty?
83
+ return { valid: false, error: :csrf_missing, message: "csrf_missing" }
84
+ end
85
+
86
+ if header_token.nil? || header_token.strip.empty?
87
+ return { valid: false, error: :csrf_mismatch, message: "csrf_missing" }
88
+ end
89
+
90
+ # Normalize tokens
91
+ cookie_normalized = cookie_token.to_s.strip
92
+ header_normalized = header_token.to_s.strip
93
+
94
+ # Use constant-time comparison to prevent timing attacks
95
+ if cookie_normalized.length != header_normalized.length
96
+ return { valid: false, error: :csrf_mismatch, message: "csrf_mismatch" }
97
+ end
98
+
99
+ unless ActiveSupport::SecurityUtils.secure_compare(cookie_normalized, header_normalized)
100
+ return { valid: false, error: :csrf_mismatch, message: "csrf_mismatch" }
101
+ end
102
+
103
+ { valid: true }
104
+ end
105
+
106
+ # Check if CSRF is enabled
107
+ # CSRF can only be enabled when cookie auth is also enabled
108
+ def csrf_enabled?
109
+ CommandTower::Jwt::AuthorizationHelper.cookie_enabled? && CommandTower.config.jwt.cookie.csrf.enabled?
110
+ end
111
+
112
+ # Get cookie options (single source of truth)
113
+ # Uses explicit nil checks (not ||) for true nil-coalescing of inheritable fields
114
+ def csrf_cookie_options(expires_at:)
115
+ config = CommandTower.config.jwt.cookie.csrf
116
+ jwt_config = CommandTower.config.jwt.cookie
117
+
118
+ # Boolean handling for secure (already fixed)
119
+ secure_value = config.secure.nil? ? (jwt_config.secure.nil? ? Rails.env.production? : jwt_config.secure) : config.secure
120
+
121
+ # Explicit nil checks for same_site, path, domain (not || to respect explicit false/nil)
122
+ same_site_value = config.same_site.nil? ? jwt_config.same_site : config.same_site
123
+ path_value = config.path.nil? ? jwt_config.path : config.path
124
+ domain_value = config.domain.nil? ? jwt_config.domain : config.domain
125
+
126
+ options = {
127
+ expires: expires_at,
128
+ httponly: false, # CRITICAL: Must be readable by JavaScript (defined once)
129
+ secure: secure_value,
130
+ same_site: same_site_value,
131
+ path: path_value
132
+ }
133
+ options[:domain] = domain_value if domain_value.present?
134
+ options
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module PasswordRecovery
5
+ module AuthorizationHelper
6
+ SCHEME = "Recovery"
7
+ HEADER = "Authorization"
8
+
9
+ module_function
10
+
11
+ def extract_token(request)
12
+ raw = request.headers[HEADER].to_s.strip
13
+ return { error: :missing } if raw.blank?
14
+
15
+ match = raw.match(/\A#{SCHEME}\s+(.+)\z/i)
16
+ return { error: :invalid_format } unless match
17
+
18
+ token = match[1].to_s.strip
19
+ return { error: :missing } if token.blank?
20
+
21
+ { token: token }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redis"
4
+
5
+ module CommandTower
6
+ module RedisConnection
7
+ module_function
8
+
9
+ def current
10
+ @current ||= Redis.new(url: ENV.fetch("REDIS_URL"))
11
+ end
12
+
13
+ def with
14
+ yield current
15
+ end
16
+
17
+ def reset!
18
+ @current = nil
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json_schematize/generator"
4
+
5
+ module CommandTower
6
+ module Schema
7
+ module Error
8
+ class EmailValidationRequired < JsonSchematize::Generator
9
+ add_field name: :status, type: String
10
+ add_field name: :message, type: String
11
+ add_field name: :meta, type: Hash
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Schema
5
+ module Shared
6
+ class User < JsonSchematize::Generator
7
+ schema_default option: :dig_type, value: :string
8
+
9
+ def self.convert_user_object(user:)
10
+ attributes = CommandTower.config.user.default_attributes.map(&:to_s)
11
+ object = user.attributes.slice(*attributes)
12
+
13
+ new(object)
14
+ end
15
+
16
+ # Gets assigned during configuration phase via
17
+ # lib/command_tower/configuration/user/config.rb
18
+ def self.assign!
19
+ attributes = CommandTower.config.user.default_attributes
20
+
21
+ attributes.each do |attribute|
22
+ if metadata = ::User.attribute_to_type_mapping[attribute]
23
+ type = metadata[:serialized_type] ? metadata[:serialized_type] : metadata[:base]
24
+ type = JsonSchematize::Boolean if type == "Boolean"
25
+ add_field(name: attribute, type:)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -7,32 +7,9 @@ module CommandTower
7
7
 
8
8
  ## Generic Error Schemas
9
9
  require "command_tower/schema/error/base"
10
+ require "command_tower/schema/error/email_validation_required"
10
11
  require "command_tower/schema/error/invalid_argument_response"
11
12
 
12
- ## Plain Text Controller
13
- require "command_tower/schema/plain_text/create_user_response"
14
- require "command_tower/schema/plain_text/create_user_request"
15
-
16
- require "command_tower/schema/plain_text/email_verify_request"
17
- require "command_tower/schema/plain_text/email_verify_response"
18
-
19
- require "command_tower/schema/plain_text/email_verify_send_response"
20
- require "command_tower/schema/plain_text/email_verify_send_request"
21
-
22
- require "command_tower/schema/plain_text/login_request"
23
- require "command_tower/schema/plain_text/login_response"
24
-
25
- require "command_tower/schema/admin/users"
26
-
27
- require "command_tower/schema/user"
28
- require "command_tower/schema/page"
29
-
30
- require "command_tower/schema/inbox/metadata"
31
- require "command_tower/schema/inbox/message_entity"
32
- require "command_tower/schema/inbox/modified"
33
- require "command_tower/schema/inbox/blast_response"
34
- require "command_tower/schema/inbox/blast_request"
35
- require "command_tower/schema/inbox/message_blast_entity"
36
- require "command_tower/schema/inbox/message_blast_metadata"
13
+ require "command_tower/schema/shared/user"
37
14
  end
38
15
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Signup
5
+ module AuthorizationHelper
6
+ SCHEME = "Signup"
7
+ HEADER = "Authorization"
8
+
9
+ module_function
10
+
11
+ def extract_token(request)
12
+ raw = request.headers[HEADER].to_s.strip
13
+ return { error: :missing } if raw.blank?
14
+
15
+ match = raw.match(/\A#{SCHEME}\s+(.+)\z/i)
16
+ return { error: :invalid_format } unless match
17
+
18
+ token = match[1].to_s.strip
19
+ return { error: :missing } if token.blank?
20
+
21
+ { token: token }
22
+ end
23
+ end
24
+ end
25
+ end