command_tower 0.13.1 → 0.15.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/app/controllers/command_tower/me/push_controller.rb +74 -0
- data/app/deserializers/command_tower/deserializers/me/push/token_deserializer.rb +42 -0
- data/app/errors/command_tower/errors/account/push_capability_unavailable_error.rb +21 -0
- data/app/errors/command_tower/errors/account/push_endpoint_not_found_error.rb +21 -0
- data/app/errors/command_tower/errors/account/push_test_no_endpoint_error.rb +21 -0
- data/app/errors/command_tower/errors/account/push_test_rate_limit_error.rb +25 -0
- data/app/serializers/command_tower/serializers/me/push_serializer.rb +47 -0
- data/app/services/command_tower/messaging/channel_detectors.rb +6 -0
- data/app/services/command_tower/messaging/endpoints/validators/push.rb +6 -0
- data/app/services/command_tower/messaging/execution/adapter_request.rb +21 -3
- data/app/services/command_tower/messaging/execution/adapters/expo/adapter.rb +229 -0
- data/app/services/command_tower/messaging/execution/adapters/expo/configuration.rb +42 -0
- data/app/services/command_tower/messaging/execution/adapters/expo/http_client.rb +137 -0
- data/app/services/command_tower/messaging/execution/resolve_recipient_address.rb +7 -0
- data/app/services/command_tower/messaging/execution/select_adapter.rb +4 -0
- data/app/services/command_tower/messaging/rendering/channel_renderer.rb +21 -3
- data/app/services/command_tower/messaging/rendering/rendered_push_payload.rb +50 -0
- data/app/services/command_tower/services/account/push/check_self_test_rate_limit.rb +41 -0
- data/app/services/command_tower/services/account/push/create.rb +25 -0
- data/app/services/command_tower/services/account/push/ct_support.rb +38 -0
- data/app/services/command_tower/services/account/push/destroy.rb +23 -0
- data/app/services/command_tower/services/account/push/list.rb +23 -0
- data/app/services/command_tower/services/account/push/replace.rb +26 -0
- data/app/services/command_tower/services/account/push/send_self_test.rb +57 -0
- data/app/services/command_tower/services/me/push_product_gate.rb +18 -0
- data/app/views/command_tower/messaging/rendering/push.text.erb +1 -0
- data/app/workflows/command_tower/workflows/me/error_mapping.rb +6 -2
- data/app/workflows/command_tower/workflows/me/push/create_workflow.rb +37 -0
- data/app/workflows/command_tower/workflows/me/push/destroy_workflow.rb +37 -0
- data/app/workflows/command_tower/workflows/me/push/index_workflow.rb +34 -0
- data/app/workflows/command_tower/workflows/me/push/replace_workflow.rb +38 -0
- data/app/workflows/command_tower/workflows/me/push/test_workflow.rb +54 -0
- data/app/workflows/command_tower/workflows/me/push/workflow_support.rb +35 -0
- data/app/workflows/command_tower/workflows/messaging/execution/deliver_workflow.rb +1 -0
- data/config/routes.rb +11 -0
- data/docs/api_reference.md +21 -0
- data/docs/controllers.md +1 -0
- data/docs/messaging_integration_guide.md +8 -3
- data/docs/upgrades/0.14.0.md +31 -0
- data/docs/upgrades/0.15.0.md +32 -0
- data/docs/upgrades/README.md +2 -0
- data/lib/command_tower/authorization/default.yml +8 -0
- data/lib/command_tower/configuration/messaging/config.rb +6 -0
- data/lib/command_tower/configuration/messaging/expo.rb +51 -0
- data/lib/command_tower/version.rb +1 -1
- metadata +31 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Workflows
|
|
5
|
+
module Me
|
|
6
|
+
module Push
|
|
7
|
+
class ReplaceWorkflow < CommandTower::Workflows::ApplicationWorkflow
|
|
8
|
+
retry_strategy :none
|
|
9
|
+
|
|
10
|
+
def call(current_user:, endpoint_id:, address:, auth_context: nil)
|
|
11
|
+
unless CommandTower::Services::Me::PushProductGate.enabled?
|
|
12
|
+
return failure(**WorkflowSupport.capability_failure)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
result = CommandTower::Services::Account::Push::Replace.call(
|
|
16
|
+
user: current_user,
|
|
17
|
+
endpoint_id:,
|
|
18
|
+
address:,
|
|
19
|
+
)
|
|
20
|
+
unless result.success?
|
|
21
|
+
error = result.errors.first
|
|
22
|
+
return failure(
|
|
23
|
+
errors: result.errors,
|
|
24
|
+
http_status: CommandTower::Workflows::Me::ErrorMapping.http_status_for(error),
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
success(
|
|
29
|
+
payload: WorkflowSupport.serialize_view(result.data[:safe_view]),
|
|
30
|
+
http_status: :ok,
|
|
31
|
+
response_effects: WorkflowSupport.expire_header_effects(auth_context),
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Workflows
|
|
5
|
+
module Me
|
|
6
|
+
module Push
|
|
7
|
+
class TestWorkflow < CommandTower::Workflows::ApplicationWorkflow
|
|
8
|
+
retry_strategy :none
|
|
9
|
+
|
|
10
|
+
def call(current_user:, auth_context: nil)
|
|
11
|
+
unless CommandTower::Services::Me::PushProductGate.enabled?
|
|
12
|
+
return failure(**WorkflowSupport.capability_failure)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
result = CommandTower::Services::Account::Push::SendSelfTest.call(user: current_user)
|
|
16
|
+
unless result.success?
|
|
17
|
+
error = result.errors.first
|
|
18
|
+
return failure(
|
|
19
|
+
errors: result.errors,
|
|
20
|
+
http_status: http_status_for(error),
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
success(
|
|
25
|
+
payload: {
|
|
26
|
+
communicationId: result.data[:communication_id],
|
|
27
|
+
destinationPlanId: result.data[:destination_plan_id],
|
|
28
|
+
selectedChannels: result.data[:selected_channels],
|
|
29
|
+
status: result.data[:communication_status],
|
|
30
|
+
},
|
|
31
|
+
http_status: :ok,
|
|
32
|
+
response_effects: WorkflowSupport.expire_header_effects(auth_context),
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def http_status_for(error)
|
|
39
|
+
case error
|
|
40
|
+
when CommandTower::Errors::ValidationError,
|
|
41
|
+
CommandTower::Errors::Messaging::RecipientUnresolvedError,
|
|
42
|
+
CommandTower::Errors::Messaging::AcceptRejectedError
|
|
43
|
+
:unprocessable_entity
|
|
44
|
+
when CommandTower::Errors::Messaging::IdempotencyConflictError
|
|
45
|
+
:conflict
|
|
46
|
+
else
|
|
47
|
+
CommandTower::Workflows::Me::ErrorMapping.http_status_for(error)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CommandTower
|
|
4
|
+
module Workflows
|
|
5
|
+
module Me
|
|
6
|
+
module Push
|
|
7
|
+
module WorkflowSupport
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def capability_failure
|
|
11
|
+
error = CommandTower::Errors::Account::PushCapabilityUnavailableError.new
|
|
12
|
+
{
|
|
13
|
+
errors: [error],
|
|
14
|
+
http_status: CommandTower::Workflows::Me::ErrorMapping.http_status_for(error),
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def expire_header_effects(auth_context)
|
|
19
|
+
return if auth_context.nil?
|
|
20
|
+
|
|
21
|
+
{ set_expire_header: auth_context.token_expires_at }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def serialize_view(safe_view)
|
|
25
|
+
CommandTower::Serializers::Me::PushSerializer.serialize(safe_view)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def serialize_collection(safe_views)
|
|
29
|
+
CommandTower::Serializers::Me::PushSerializer.serialize_collection(safe_views)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -114,6 +114,7 @@ module CommandTower
|
|
|
114
114
|
channel_key: delivery.channel_key,
|
|
115
115
|
attempt_id: attempt.id,
|
|
116
116
|
rendered:,
|
|
117
|
+
eligible_endpoint_ids: Array(readiness.eligible_endpoint_ids),
|
|
117
118
|
)
|
|
118
119
|
adapter = CommandTower::Messaging::Execution::SelectAdapter.call(delivery:, executor:)
|
|
119
120
|
CommandTower::Messaging::Execution::OperationLogger.adapter_started(
|
data/config/routes.rb
CHANGED
|
@@ -100,6 +100,17 @@ CommandTower::Engine.routes.draw do
|
|
|
100
100
|
put "pushover", to: "pushover#update"
|
|
101
101
|
delete "pushover", to: "pushover#destroy"
|
|
102
102
|
post "pushover/verification", to: "pushover/verifications#create"
|
|
103
|
+
|
|
104
|
+
# Expo push lifecycle always drawn (no route constraints).
|
|
105
|
+
# Product readiness is workflow-gated as 503 push_capability_unavailable.
|
|
106
|
+
# Collection JSON (multi-active). No verification POST — create/replace mark_verified.
|
|
107
|
+
# Self-test must be declared before :id routes so "test" is not captured as an id.
|
|
108
|
+
get "push", to: "push#index"
|
|
109
|
+
post "push", to: "push#create"
|
|
110
|
+
post "push/test", to: "push#test"
|
|
111
|
+
patch "push/:id", to: "push#update"
|
|
112
|
+
put "push/:id", to: "push#update"
|
|
113
|
+
delete "push/:id", to: "push#destroy"
|
|
103
114
|
end
|
|
104
115
|
|
|
105
116
|
namespace :admin do
|
data/docs/api_reference.md
CHANGED
|
@@ -426,6 +426,27 @@ Errors include `422` (`pushover_already_configured`, `pushover_not_configured`,
|
|
|
426
426
|
|
|
427
427
|
---
|
|
428
428
|
|
|
429
|
+
## Push (Expo)
|
|
430
|
+
|
|
431
|
+
Routes are **always drawn**. Product readiness is workflow-gated as **503** `push_capability_unavailable` (`config.messaging.expo.adapter` must be `fake`, `log`, or `http`).
|
|
432
|
+
|
|
433
|
+
There is **no** `POST /me/push/verification`. Create and replace call `Endpoints.mark_verified` in-workflow after persist.
|
|
434
|
+
|
|
435
|
+
| Method | Path | Body | Notes |
|
|
436
|
+
|--------|------|------|--------|
|
|
437
|
+
| `GET` | `/me/push` | — | `{ endpoints: [...] }` — **active only** (terminal rows omitted) |
|
|
438
|
+
| `POST` | `/me/push` | `token`/`address` (snake or camelCase) | Create or idempotent same-fingerprint; returns verified endpoint |
|
|
439
|
+
| `PATCH` / `PUT` | `/me/push/:id` | same | Replace by id; new row verified; other actives untouched |
|
|
440
|
+
| `DELETE` | `/me/push/:id` | — | Revoke; returns revoked SafeView |
|
|
441
|
+
|
|
442
|
+
**Token form:** `ExponentPushToken[...]` or `ExpoPushToken[...]` (422 otherwise).
|
|
443
|
+
|
|
444
|
+
**Endpoint fields:** `id`, `channelKey`, `lifecycleState`, `verificationState`, `maskedDisplayValue`, `verifiedAt`, `createdAt`, `updatedAt`, `actions: { canReplace, canRemove }`. Never the raw token.
|
|
445
|
+
|
|
446
|
+
**RBAC:** `me_push`. **Spec:** `spec/requests/command_tower/me/push_spec.rb`.
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
429
450
|
## Admin Workspace
|
|
430
451
|
|
|
431
452
|
### `GET /admin/workspace`
|
data/docs/controllers.md
CHANGED
|
@@ -20,6 +20,7 @@ This page is an **index** of route areas. Detailed request/response contracts li
|
|
|
20
20
|
| Preferences | `/me/preferences*` | Notification preferences |
|
|
21
21
|
| Phone | `/me/phone*` | Phone endpoint + verification |
|
|
22
22
|
| Pushover | `/me/pushover*` | Pushover endpoint lifecycle + verification |
|
|
23
|
+
| Push | `/me/push*` | Expo push endpoint collection (register / replace / revoke) |
|
|
23
24
|
| Admin messaging | `/admin/messaging/announcements` | Cohort announcements |
|
|
24
25
|
|
|
25
26
|
Exact paths depend on where the host mounts the engine.
|
|
@@ -10,7 +10,8 @@ Complete install and host RBAC first: [Host integration](host_integration_guide.
|
|
|
10
10
|
|---------|----------------|
|
|
11
11
|
| User inbox (consume) | Engine `/me/inbox*` — see [API reference](api_reference.md#me-inbox) |
|
|
12
12
|
| Preferences | Engine `/me/preferences*` — see [API reference](api_reference.md#preferences) |
|
|
13
|
-
| Phone / Pushover endpoints | Engine `/me/phone*`, `/me/pushover*` (503 when capability unavailable) |
|
|
13
|
+
| Phone / Pushover / Push endpoints | Engine `/me/phone*`, `/me/pushover*`, `/me/push*` (503 when capability unavailable) |
|
|
14
|
+
|
|
14
15
|
| Single-recipient emit | `CommandTower::Services::Messaging::Communications::Produce` |
|
|
15
16
|
| Multi-recipient emit | `CommandTower::Services::Messaging::Communications::ProduceMany` |
|
|
16
17
|
| Admin cohort announce | Engine `POST /admin/messaging/announcements` |
|
|
@@ -73,10 +74,14 @@ Pagination detail: [pagination.md](pagination.md). Full catalog: [api_reference.
|
|
|
73
74
|
|
|
74
75
|
- Notification catalog content (registered into CommandTower notification types)
|
|
75
76
|
- `platform_enabled_channels` / channel policy injection
|
|
76
|
-
- Messaging adapter credentials (email / SMS / Pushover) via initializer or ENV
|
|
77
|
-
- Host product roles that grant CT-owned Me inbox/preferences/phone/pushover entities (and `admin` if used)
|
|
77
|
+
- Messaging adapter credentials (email / SMS / Pushover / Expo push) via initializer or ENV
|
|
78
|
+
- Host product roles that grant CT-owned Me inbox/preferences/phone/pushover/`me_push` entities (and `admin` if used)
|
|
78
79
|
- Product-specific operational tooling (announce rake tasks, welcome copy)
|
|
79
80
|
|
|
81
|
+
Expo push (`config.messaging.expo`): set `adapter` to `fake`, `log`, or `http` to enable the Messaging `push` channel and Me `/me/push*` (default `disabled`). `access_token` is optional — send `Authorization: Bearer` only when non-blank (required only if the Expo project enables enhanced push security). Never log the token.
|
|
82
|
+
|
|
83
|
+
Me push registration: engine `/me/push*` (collection of active endpoints). Create/replace persist via `Endpoints` and **mark_verified** in-workflow — there is no `POST /me/push/verification`. Tokens must be Expo form (`ExponentPushToken[...]` / `ExpoPushToken[...]`).
|
|
84
|
+
|
|
80
85
|
## Related
|
|
81
86
|
|
|
82
87
|
- [Controllers / routes](controllers.md)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Upgrade: CommandTower 0.14.0
|
|
2
|
+
|
|
3
|
+
**From:** `0.13.1`
|
|
4
|
+
**To:** `0.14.0`
|
|
5
|
+
|
|
6
|
+
Minor release: Expo push as an executable Messaging channel, plus engine `/me/push*` registration HTTP. No schema migration.
|
|
7
|
+
|
|
8
|
+
## Host-visible changes
|
|
9
|
+
|
|
10
|
+
| Change | Host impact |
|
|
11
|
+
|--------|-------------|
|
|
12
|
+
| `config.messaging.expo` | New composer: `adapter` (`disabled` / `fake` / `log` / `http`) and optional `access_token` (Bearer sent only when non-blank). Required for `push` to be platform-configured. |
|
|
13
|
+
| Channel `push` execution | Detector, renderer (`RenderedPushPayload`), Expo adapter (send + one getReceipts pass), multi-endpoint fan-out via `AdapterRequest.eligible_endpoint_ids`. `DeviceNotRegistered` → `Endpoints.mark_invalid`. |
|
|
14
|
+
| `/api/me/push*` | Collection register / list (active) / replace / revoke. Create/replace call `mark_verified` in-workflow (no verification POST). 503 when Expo adapter is disabled. |
|
|
15
|
+
| RBAC `me_push` | Show/create/update/destroy for Me push. Hosts must grant on roles that should register device tokens (e.g. `member`). |
|
|
16
|
+
| `Validators::Push` | Requires Expo token form (`ExponentPushToken[…]` / `ExpoPushToken[…]`). |
|
|
17
|
+
|
|
18
|
+
## Host actions
|
|
19
|
+
|
|
20
|
+
1. Bump gem to `0.14.0` (or `>= 0.14.0`).
|
|
21
|
+
2. **No new migration.**
|
|
22
|
+
3. Set `config.messaging.expo.adapter` (use `http` on production TF API; `fake` is fine for development/test with `allow_fake_adapter`).
|
|
23
|
+
4. Optionally set `config.messaging.expo.access_token` **only if** the Expo project has enhanced push security enabled.
|
|
24
|
+
5. Enable `push` in `platform_enabled_channels` and grant `me_push` when the host product should register/send push.
|
|
25
|
+
6. Add `push` to announcement (or other) message types’ allowed/default channels when Planner should select it.
|
|
26
|
+
|
|
27
|
+
## Not in this release
|
|
28
|
+
|
|
29
|
+
- Host channel policy / Pick’em enablement (host repo)
|
|
30
|
+
- Native client token lifecycle / CT FE `beforeLogout`
|
|
31
|
+
- EAS/APNs credentials or TestFlight proof
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Upgrade: CommandTower 0.15.0
|
|
2
|
+
|
|
3
|
+
**From:** `0.14.0`
|
|
4
|
+
**To:** `0.15.0`
|
|
5
|
+
|
|
6
|
+
Minor release: Me push self-test (`POST /me/push/test`) through Messaging Produce, with configurable rate limits. No schema migration.
|
|
7
|
+
|
|
8
|
+
## Host-visible changes
|
|
9
|
+
|
|
10
|
+
| Change | Host impact |
|
|
11
|
+
|--------|-------------|
|
|
12
|
+
| `POST /api/me/push/test` | Authenticated self-test. Produces `push_delivery_test` via Communications::Produce (Expo path). Requires at least one active push endpoint. Returns Produce identifiers only (no Expo tokens). |
|
|
13
|
+
| RBAC `me_push` | Adds action `test` (same entity). Hosts that already grant `me_push` get the new action. |
|
|
14
|
+
| `config.messaging.expo.self_test_per_user_hour` | Max self-test sends per user per rolling window (default **10**). |
|
|
15
|
+
| `config.messaging.expo.self_test_window_seconds` | Rolling window length (default **3600**). |
|
|
16
|
+
| Rate limit HTTP | Exceeded limit → **429** `push_test_rate_limited` with `retry_after_seconds` details (existing CT envelope). |
|
|
17
|
+
| No endpoint | **422** `push_test_no_endpoint`. |
|
|
18
|
+
| Gate off | **503** `push_capability_unavailable` (same as other `/me/push*` when Expo adapter disabled). |
|
|
19
|
+
|
|
20
|
+
## Host actions
|
|
21
|
+
|
|
22
|
+
1. Bump gem to `0.15.0` (or `>= 0.15.0`).
|
|
23
|
+
2. **No new migration.**
|
|
24
|
+
3. **Required before enabling Account Push notifications settings:** register notification type `push_delivery_test` with push-only plan (e.g. `allowed/default %w[push]`, `inbox_available: false`, `user_configurable: false`, not preference-visible). CT hardcodes this type key for Me self-test.
|
|
25
|
+
4. Set `config.messaging.expo.self_test_per_user_hour` explicitly when product policy differs from the default (Pick’em: **10**).
|
|
26
|
+
5. Keep Expo adapter enabled (`http` / `fake`) and `me_push` granted for roles that use push.
|
|
27
|
+
|
|
28
|
+
## Not in this release
|
|
29
|
+
|
|
30
|
+
- CT FE Account Push notifications settings / device lifecycle migration
|
|
31
|
+
- Host Pick’em enablement / type registration / IPA
|
|
32
|
+
- Physical TestFlight proof
|
data/docs/upgrades/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Host-facing upgrade / change summaries for CommandTower releases.
|
|
|
4
4
|
|
|
5
5
|
| Version | Summary |
|
|
6
6
|
|---------|---------|
|
|
7
|
+
| [0.15.0](0.15.0.md) | Me `POST /me/push/test` → Produce (`push_delivery_test`); configurable Expo self-test rate limit composers; `me_push#test` |
|
|
8
|
+
| [0.14.0](0.14.0.md) | Expo push Messaging channel (`config.messaging.expo`) + `/api/me/push*` registration HTTP; `me_push` RBAC |
|
|
7
9
|
| [0.13.1](0.13.1.md) | Canonical `Intervention::Severity` constants (`blocking`, `warning`, `informational`) |
|
|
8
10
|
| [0.13.0](0.13.0.md) | Account self-service deletion (`DELETE /api/me/account`); tombstone + PII scrub; `me_account` RBAC; `users.deleted_at` migration |
|
|
9
11
|
| [0.12.0](0.12.0.md) | Intervention envelope serializers/deserializers; product-tool admin_scope without Users/Audit narrowing |
|
|
@@ -68,6 +68,14 @@ entities:
|
|
|
68
68
|
- name: me_pushover_verification
|
|
69
69
|
controller: CommandTower::Me::Pushover::VerificationsController
|
|
70
70
|
only: create
|
|
71
|
+
- name: me_push
|
|
72
|
+
controller: CommandTower::Me::PushController
|
|
73
|
+
only:
|
|
74
|
+
- index
|
|
75
|
+
- create
|
|
76
|
+
- update
|
|
77
|
+
- destroy
|
|
78
|
+
- test
|
|
71
79
|
- name: me_audit_events
|
|
72
80
|
controller: CommandTower::Me::AuditEventsController
|
|
73
81
|
only:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "class_composer"
|
|
4
4
|
require "command_tower/configuration/messaging/sms"
|
|
5
5
|
require "command_tower/configuration/messaging/pushover"
|
|
6
|
+
require "command_tower/configuration/messaging/expo"
|
|
6
7
|
|
|
7
8
|
module CommandTower
|
|
8
9
|
module Configuration
|
|
@@ -54,6 +55,11 @@ module CommandTower
|
|
|
54
55
|
desc: "Pushover verification transport configuration (credential validate + test notification)",
|
|
55
56
|
allowed: Pushover,
|
|
56
57
|
default: Pushover.new
|
|
58
|
+
|
|
59
|
+
add_composer :expo,
|
|
60
|
+
desc: "Expo Push delivery configuration (Messaging push channel; optional access_token)",
|
|
61
|
+
allowed: Expo,
|
|
62
|
+
default: Expo.new
|
|
57
63
|
end
|
|
58
64
|
end
|
|
59
65
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "class_composer"
|
|
4
|
+
|
|
5
|
+
module CommandTower
|
|
6
|
+
module Configuration
|
|
7
|
+
module Messaging
|
|
8
|
+
class Expo < ::CommandTower::Configuration::Base
|
|
9
|
+
include ClassComposer::Generator
|
|
10
|
+
|
|
11
|
+
ADAPTERS = %w[disabled fake log http].freeze
|
|
12
|
+
|
|
13
|
+
add_composer :adapter,
|
|
14
|
+
desc: "Expo Push delivery: disabled | fake | log | http. Default disabled (fail closed).",
|
|
15
|
+
allowed: String,
|
|
16
|
+
default: "disabled",
|
|
17
|
+
validator: ->(val) { ADAPTERS.include?(val.to_s) },
|
|
18
|
+
invalid_message: ->(val) { "Provided #{val.inspect}. Allowed: #{ADAPTERS.join(', ')}" }
|
|
19
|
+
|
|
20
|
+
add_composer :api_base_url,
|
|
21
|
+
desc: "Expo Push API origin (send = {base}/send, receipts = {base}/getReceipts).",
|
|
22
|
+
allowed: String,
|
|
23
|
+
default: "https://exp.host/--/api/v2/push"
|
|
24
|
+
|
|
25
|
+
add_composer :timeout_seconds,
|
|
26
|
+
desc: "HTTP open/read timeout seconds for Expo Push provider calls.",
|
|
27
|
+
allowed: Integer,
|
|
28
|
+
default: 5
|
|
29
|
+
|
|
30
|
+
add_composer :access_token,
|
|
31
|
+
desc: "Optional Expo Push access token. When non-blank, send Authorization: Bearer. " \
|
|
32
|
+
"Required only if the Expo project enables enhanced push security. Never log.",
|
|
33
|
+
allowed: String,
|
|
34
|
+
default: ""
|
|
35
|
+
|
|
36
|
+
add_composer :self_test_per_user_hour,
|
|
37
|
+
desc: "Max Me push self-test (POST /me/push/test) sends allowed per user per rolling window. " \
|
|
38
|
+
"Hosts enabling Account Push notifications settings should set this explicitly " \
|
|
39
|
+
"(Pick'em: 10).",
|
|
40
|
+
allowed: Integer,
|
|
41
|
+
default: 10
|
|
42
|
+
|
|
43
|
+
add_composer :self_test_window_seconds,
|
|
44
|
+
desc: "Rolling window length in seconds for Me push self-test rate limiting " \
|
|
45
|
+
"(default 3600 = one hour).",
|
|
46
|
+
allowed: Integer,
|
|
47
|
+
default: 3600
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: command_tower
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- matt-taylor
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: phonelib
|
|
@@ -218,6 +218,7 @@ files:
|
|
|
218
218
|
- app/controllers/command_tower/me/phone_verification/verifications_controller.rb
|
|
219
219
|
- app/controllers/command_tower/me/phone_verification/verify_controller.rb
|
|
220
220
|
- app/controllers/command_tower/me/preferences_controller.rb
|
|
221
|
+
- app/controllers/command_tower/me/push_controller.rb
|
|
221
222
|
- app/controllers/command_tower/me/pushover/verifications_controller.rb
|
|
222
223
|
- app/controllers/command_tower/me/pushover_controller.rb
|
|
223
224
|
- app/controllers/command_tower/me_controller.rb
|
|
@@ -254,6 +255,7 @@ files:
|
|
|
254
255
|
- app/deserializers/command_tower/deserializers/me/change_password_deserializer.rb
|
|
255
256
|
- app/deserializers/command_tower/deserializers/me/delete_account_deserializer.rb
|
|
256
257
|
- app/deserializers/command_tower/deserializers/me/phone_verification/verify_deserializer.rb
|
|
258
|
+
- app/deserializers/command_tower/deserializers/me/push/token_deserializer.rb
|
|
257
259
|
- app/deserializers/command_tower/deserializers/me/pushover/credentials_deserializer.rb
|
|
258
260
|
- app/deserializers/command_tower/deserializers/me/update_name_deserializer.rb
|
|
259
261
|
- app/deserializers/command_tower/deserializers/me/update_phone_deserializer.rb
|
|
@@ -267,6 +269,10 @@ files:
|
|
|
267
269
|
- app/errors/command_tower/errors/account/phone_verification_send_failed_error.rb
|
|
268
270
|
- app/errors/command_tower/errors/account/phone_verification_stale_error.rb
|
|
269
271
|
- app/errors/command_tower/errors/account/phone_verification_throttled_error.rb
|
|
272
|
+
- app/errors/command_tower/errors/account/push_capability_unavailable_error.rb
|
|
273
|
+
- app/errors/command_tower/errors/account/push_endpoint_not_found_error.rb
|
|
274
|
+
- app/errors/command_tower/errors/account/push_test_no_endpoint_error.rb
|
|
275
|
+
- app/errors/command_tower/errors/account/push_test_rate_limit_error.rb
|
|
270
276
|
- app/errors/command_tower/errors/account/pushover_already_configured_error.rb
|
|
271
277
|
- app/errors/command_tower/errors/account/pushover_capability_unavailable_error.rb
|
|
272
278
|
- app/errors/command_tower/errors/account/pushover_not_configured_error.rb
|
|
@@ -363,6 +369,7 @@ files:
|
|
|
363
369
|
- app/serializers/command_tower/serializers/me/account_serializer.rb
|
|
364
370
|
- app/serializers/command_tower/serializers/me/change_password_response_serializer.rb
|
|
365
371
|
- app/serializers/command_tower/serializers/me/delete_account_response_serializer.rb
|
|
372
|
+
- app/serializers/command_tower/serializers/me/push_serializer.rb
|
|
366
373
|
- app/serializers/command_tower/serializers/me/pushover_serializer.rb
|
|
367
374
|
- app/serializers/command_tower/serializers/messaging/inbox.rb
|
|
368
375
|
- app/serializers/command_tower/serializers/messaging/preferences/catalog_serializer.rb
|
|
@@ -486,6 +493,9 @@ files:
|
|
|
486
493
|
- app/services/command_tower/messaging/execution/adapter_result.rb
|
|
487
494
|
- app/services/command_tower/messaging/execution/adapters/email/adapter.rb
|
|
488
495
|
- app/services/command_tower/messaging/execution/adapters/email/configuration.rb
|
|
496
|
+
- app/services/command_tower/messaging/execution/adapters/expo/adapter.rb
|
|
497
|
+
- app/services/command_tower/messaging/execution/adapters/expo/configuration.rb
|
|
498
|
+
- app/services/command_tower/messaging/execution/adapters/expo/http_client.rb
|
|
489
499
|
- app/services/command_tower/messaging/execution/adapters/fake_adapter.rb
|
|
490
500
|
- app/services/command_tower/messaging/execution/adapters/pushover/adapter.rb
|
|
491
501
|
- app/services/command_tower/messaging/execution/adapters/pushover/configuration.rb
|
|
@@ -579,6 +589,7 @@ files:
|
|
|
579
589
|
- app/services/command_tower/messaging/rendering/channel_renderer.rb
|
|
580
590
|
- app/services/command_tower/messaging/rendering/render_error.rb
|
|
581
591
|
- app/services/command_tower/messaging/rendering/rendered_payload.rb
|
|
592
|
+
- app/services/command_tower/messaging/rendering/rendered_push_payload.rb
|
|
582
593
|
- app/services/command_tower/messaging/rendering/rendered_pushover_payload.rb
|
|
583
594
|
- app/services/command_tower/messaging/rendering/rendered_sms_payload.rb
|
|
584
595
|
- app/services/command_tower/secrets.rb
|
|
@@ -590,6 +601,13 @@ files:
|
|
|
590
601
|
- app/services/command_tower/services/account/clear_phone.rb
|
|
591
602
|
- app/services/command_tower/services/account/phone_verification/send.rb
|
|
592
603
|
- app/services/command_tower/services/account/phone_verification/verify.rb
|
|
604
|
+
- app/services/command_tower/services/account/push/check_self_test_rate_limit.rb
|
|
605
|
+
- app/services/command_tower/services/account/push/create.rb
|
|
606
|
+
- app/services/command_tower/services/account/push/ct_support.rb
|
|
607
|
+
- app/services/command_tower/services/account/push/destroy.rb
|
|
608
|
+
- app/services/command_tower/services/account/push/list.rb
|
|
609
|
+
- app/services/command_tower/services/account/push/replace.rb
|
|
610
|
+
- app/services/command_tower/services/account/push/send_self_test.rb
|
|
593
611
|
- app/services/command_tower/services/account/pushover/create.rb
|
|
594
612
|
- app/services/command_tower/services/account/pushover/ct_support.rb
|
|
595
613
|
- app/services/command_tower/services/account/pushover/destroy.rb
|
|
@@ -642,6 +660,7 @@ files:
|
|
|
642
660
|
- app/services/command_tower/services/me/capabilities.rb
|
|
643
661
|
- app/services/command_tower/services/me/change_password.rb
|
|
644
662
|
- app/services/command_tower/services/me/delete_account.rb
|
|
663
|
+
- app/services/command_tower/services/me/push_product_gate.rb
|
|
645
664
|
- app/services/command_tower/services/me/pushover_product_gate.rb
|
|
646
665
|
- app/services/command_tower/services/me/sms_product_gate.rb
|
|
647
666
|
- app/services/command_tower/services/me/update_name.rb
|
|
@@ -660,6 +679,7 @@ files:
|
|
|
660
679
|
- app/views/command_tower/email_verification_mailer/verify_email.html.erb
|
|
661
680
|
- app/views/command_tower/messaging/rendering/email.html.erb
|
|
662
681
|
- app/views/command_tower/messaging/rendering/email.text.erb
|
|
682
|
+
- app/views/command_tower/messaging/rendering/push.text.erb
|
|
663
683
|
- app/views/command_tower/messaging/rendering/pushover.text.erb
|
|
664
684
|
- app/views/command_tower/messaging/rendering/sms.text.erb
|
|
665
685
|
- app/views/command_tower/password_reset_mailer/reset_password.html.erb
|
|
@@ -720,6 +740,12 @@ files:
|
|
|
720
740
|
- app/workflows/command_tower/workflows/me/error_status.rb
|
|
721
741
|
- app/workflows/command_tower/workflows/me/phone_verification/send_workflow.rb
|
|
722
742
|
- app/workflows/command_tower/workflows/me/phone_verification/verify_workflow.rb
|
|
743
|
+
- app/workflows/command_tower/workflows/me/push/create_workflow.rb
|
|
744
|
+
- app/workflows/command_tower/workflows/me/push/destroy_workflow.rb
|
|
745
|
+
- app/workflows/command_tower/workflows/me/push/index_workflow.rb
|
|
746
|
+
- app/workflows/command_tower/workflows/me/push/replace_workflow.rb
|
|
747
|
+
- app/workflows/command_tower/workflows/me/push/test_workflow.rb
|
|
748
|
+
- app/workflows/command_tower/workflows/me/push/workflow_support.rb
|
|
723
749
|
- app/workflows/command_tower/workflows/me/pushover/create_workflow.rb
|
|
724
750
|
- app/workflows/command_tower/workflows/me/pushover/destroy_workflow.rb
|
|
725
751
|
- app/workflows/command_tower/workflows/me/pushover/replace_workflow.rb
|
|
@@ -776,6 +802,8 @@ files:
|
|
|
776
802
|
- docs/upgrades/0.12.0.md
|
|
777
803
|
- docs/upgrades/0.13.0.md
|
|
778
804
|
- docs/upgrades/0.13.1.md
|
|
805
|
+
- docs/upgrades/0.14.0.md
|
|
806
|
+
- docs/upgrades/0.15.0.md
|
|
779
807
|
- docs/upgrades/README.md
|
|
780
808
|
- lib/command_tower.rb
|
|
781
809
|
- lib/command_tower/admin_scope.rb
|
|
@@ -822,6 +850,7 @@ files:
|
|
|
822
850
|
- lib/command_tower/configuration/login/strategy/plain_text/lockable.rb
|
|
823
851
|
- lib/command_tower/configuration/login/strategy/plain_text/password_reset.rb
|
|
824
852
|
- lib/command_tower/configuration/messaging/config.rb
|
|
853
|
+
- lib/command_tower/configuration/messaging/expo.rb
|
|
825
854
|
- lib/command_tower/configuration/messaging/pushover.rb
|
|
826
855
|
- lib/command_tower/configuration/messaging/sms.rb
|
|
827
856
|
- lib/command_tower/configuration/otp/config.rb
|