1631786ce2
Add push contracts, provider delivery and channel diagnostics. Link email channels to services and document migration and validation. Ref: IT-1074
169 lines
12 KiB
Markdown
169 lines
12 KiB
Markdown
# Push channels and Android FCM — IT-1074
|
|
|
|
Notification Service owns Firebase configuration. Invemory owns installation/user bindings
|
|
and notification preferences; it publishes a push request without Firebase credentials or OAuth access tokens.
|
|
The public Contracts package must be delivered first; updating Invemory is a separate step.
|
|
|
|
## Administration
|
|
|
|
The **Push Channels** menu opens /admin/push-channels. Create/Edit use the existing service
|
|
catalog (ServiceId UUID), Firebase Project ID, service account JSON and Active. Fields use
|
|
compact desktop widths and wrap on small screens. A service has one push channel, enforced
|
|
by a unique ServiceId index. There is no priority/fallback or extra routing configuration.
|
|
Inactive channels are retained but never selected for sending. Reassignment is allowed;
|
|
service rename automatically updates the displayed name without breaking the FK.
|
|
|
|
Creating a channel requires a valid service-account JSON whose project_id matches the
|
|
Firebase Project ID. The server validates its type, email and RSA private key without
|
|
contacting Google. Editing with an empty credentials field preserves the stored value;
|
|
changing the project requires replacement credentials. An inactive channel also requires
|
|
valid credentials, so activation does not introduce a partly configured channel.
|
|
|
|
Safe list/edit models contain no secret. Edit does not load credentials into the browser,
|
|
and validation failures clear both the model and attempted ModelState value before rendering.
|
|
Credential parser/provider errors use fixed messages and never log the JSON/private key.
|
|
The Worker constructs Google credentials from the validated service-account email/private
|
|
key; supplied token_uri or other endpoint fields are not used. There is no ADC fallback.
|
|
|
|
Storage follows the existing SMTP approach: credentials are server-only JSONB in the
|
|
Notification Service database, without new application-level encryption. Restrict access
|
|
to this private administration app, its database and backups. Existing private-network
|
|
access assumptions are unchanged; this feature adds no public administration endpoint.
|
|
|
|
## Persistence and migration
|
|
|
|
Apply 20261024080457_AddPushChannels before opening Push Channels or enabling delivery.
|
|
The additive migration creates push_channels with a required services.id FK using RESTRICT
|
|
and unique service_id index. It does not alter existing email channels, settings or usage.
|
|
Service deletion is blocked while either email or push channels still reference it; reassign
|
|
a push channel before deleting its service. Saves reuse the service administration transaction
|
|
lock to serialize service membership and duplicate-channel checks. Migration downgrade drops
|
|
push channels and their credentials, so it is destructive after channels have been configured.
|
|
|
|
The existing DAL.EF/update-development-database.bat applies the schema to the database from
|
|
the Web Development configuration; it does not provision containers or restart Web/Worker.
|
|
|
|
## Worker and contracts
|
|
|
|
Set FcmPush__Enabled=true to register the push consumer. Firebase project/credentials and
|
|
service routing come from the active channel, not environment settings or producer payloads.
|
|
The previous draft FcmPush__ServiceName host filter is removed: each request's exact current
|
|
ServiceName resolves to services.id and then that service's active push channel. Missing
|
|
services, missing/inactive channels and invalid stored configuration produce safe terminal
|
|
failure results without contacting Firebase. No legacy service-name alias is used at runtime.
|
|
|
|
SendPushMessageData retains registration/binding/user identity, ServiceName, device Token
|
|
and TokenHash, title/body, version, /home route and ExpiresAt. FcmProjectId, FcmAccessToken and
|
|
FcmAccessTokenExpiresAt have been removed. SupportedMessageType remains public in Contracts;
|
|
the existing Notification.SendPush.v1 header and notification.send-push queue are unchanged.
|
|
OAuth tokens are obtained by the Firebase SDK inside Notification Service from the channel's
|
|
service account. They are never included in RabbitMQ request/result contracts.
|
|
|
|
This replaces the unpublished draft that obtained OAuth authorization in Invemory. Source
|
|
clients still initializing the removed properties must update after the new Contracts package
|
|
is explicitly published. Old payloads containing additional JSON fields are not a configuration
|
|
source; the server ignores those fields and still requires its own active channel. Do not
|
|
resume producers that send Firebase secrets until that separate Invemory update removes them.
|
|
No Invemory source or production configuration is changed by this task.
|
|
|
|
The Worker sends bounded version-1 data-only messages with /home routes and a maximum
|
|
15-minute expiry, preserving Android binding checks before display. Delivery outcomes are
|
|
persisted by service/message ID under an advisory lock and replayed after reply failures.
|
|
A crash after FCM acceptance but before DB commit may still repeat delivery; this is not
|
|
exactly-once delivery. Expired messages are not sent.
|
|
|
|
UNREGISTERED invalidates only the matching registration/binding/token-hash. Sender mismatch,
|
|
invalid argument and authorization/configuration failures do not invalidate device tokens.
|
|
Transient delivery uses the existing three attempts, exponential delay, jitter and Retry-After.
|
|
Outcomes expire after 30 days; existing outcome/retry metrics contain no target/token labels.
|
|
Firebase acceptance does not guarantee Android display (force-stop, Doze and connectivity apply).
|
|
|
|
## Validation boundaries
|
|
|
|
Tests cover the administration lifecycle, secret non-disclosure, unchanged-secret edits,
|
|
service reassignment/rename, inactive selection, duplicate/missing services, project changes,
|
|
worker name-to-ID resolution, server credential selection and credential-free Contracts.
|
|
All provider routing tests use a fake Firebase client. Real Firebase delivery, credential
|
|
permissions and Android notification display require a separate end-to-end verification
|
|
with an authorized configured project/device; fake local credentials cannot prove those.
|
|
|
|
On 2026-10-24 the Release solution build and targeted tests passed (21 unit and
|
|
24 integration). Rider inspection reported no errors in the changed production classes;
|
|
the existing Firebase SDK Token deprecation warning remains. Structural review kept
|
|
changed methods within cognitive complexity 10; the tool did not report numeric scores.
|
|
The review compared email-channel mapping/editor patterns, service catalog controllers,
|
|
transaction locking and existing push delivery/retry behavior. No unresolved blocking
|
|
findings remained for the implemented scope.
|
|
|
|
Desktop browser validation used a disposable database and Web at 127.0.0.1:52658, with
|
|
an offline-generated test service account (no Google account or network send): empty
|
|
list, create, invalid JSON feedback/secret clearing, edit without secret disclosure, and
|
|
Active off with credentials preserved all passed. Mobile wrapping was inspected in CSS
|
|
but not exercised on an Android device. The temporary Web and database container were
|
|
removed after validation. The owner-applied AddPushChannels migration was separately
|
|
confirmed in the existing development database; it was not reapplied.
|
|
|
|
## Channel tests — 2026-10-24
|
|
|
|
The editor has separate **Check Firebase authorization** and **Send test push** actions,
|
|
following the current-form SMTP test workflow. Neither action saves a channel. New channels
|
|
use the entered service-account JSON; blank JSON on an existing channel uses its stored
|
|
credentials only if the project is unchanged. Inactive channels can be tested explicitly.
|
|
Both POST endpoints require the form antiforgery token and bounded request bodies.
|
|
The existing private-network/port-forward access boundary is retained: this Web application
|
|
has no in-app authentication or administrator-role middleware. Do not expose it publicly.
|
|
|
|
Authorization requests an OAuth access token with the firebase.messaging scope and a
|
|
30-second timeout. It neither requires a recipient nor calls FCM send. A successful OAuth
|
|
exchange proves service-account authentication only, not FCM API enablement, send permission
|
|
or device delivery. The access token is never returned, saved or logged.
|
|
|
|
Send opens a separate Recipient token field and sends a neutral notification message through
|
|
the same FirebasePushClient transport using current form credentials. RecipientToken is not
|
|
part of the save model or channel storage. The dialog clears it after the attempt or close.
|
|
Only test on an explicitly supplied, owned test-device token. Provider acceptance is shown
|
|
as acceptance, never as verified delivery/display. This direct provider diagnostic does not
|
|
exercise Invemory's data-only binding, expiry/tap checks, transactional Outbox or result queue;
|
|
it does not replace that separate end-to-end validation. Foreground display depends on the
|
|
receiving app. No user inventory or account data is included.
|
|
|
|
Errors are fixed, safe messages; no provider exception, credentials, access token or recipient
|
|
is echoed. The UI uses textContent for results. Saved credentials never populate the editor.
|
|
|
|
### Channel-test verification
|
|
|
|
Release solution build passed. 29 focused push unit tests and 9 PushChannelAdmin integration
|
|
tests passed, including CSRF rejection, missing credentials, unchanged/changed project reuse,
|
|
unsaved credentials, separate authorization/send calls, transient errors and no persistence.
|
|
Rider's final changed-file error inspection was clean. Existing FirebaseAdmin Message.Token
|
|
obsolescence and Microsoft.OpenApi vulnerability warnings remain outside this task.
|
|
Cognitive complexity was manually reviewed, not numerically measured.
|
|
|
|
A temporary local Web instance on 127.0.0.1:52658 verified both buttons, modal recipient input,
|
|
empty-token validation, missing-credential response and clearing the recipient after a failed
|
|
attempt. Only a fake non-deliverable token with missing credentials was submitted; the
|
|
request stopped before Firebase. No channel was created or modified by this UI verification.
|
|
Code review compared current SMTP test behavior, existing credential parser/provider,
|
|
Razor editor layout, antiforgery and repository interfaces. Fixed script initialization to
|
|
wait until footer buttons exist and rejected malformed/oversized test requests safely.
|
|
The final review found no further actionable issue in these two actions.
|
|
|
|
The owner subsequently configured the local channel, verified OAuth authorization and sent
|
|
a real test to the current Samsung token on 2026-10-24. The owner confirmed notification
|
|
display and opening the installed application on tap. This validates the direct Firebase
|
|
notification test, not the Invemory data-only/outbox flow. The installed receiver intentionally
|
|
validates data-only binding fields; a notification-only test received while Invemory is in
|
|
the foreground does not satisfy that contract. Background notification display is handled
|
|
by the Firebase SDK. No additional diagnostic messages were sent by the agent.
|
|
|
|
The authorization button and its result are directly beneath the service-account JSON
|
|
field. The recipient input and Send test push button remain in the separate test dialog.
|
|
|
|
Delivery verification: the full Release solution build and all 221 tests passed (67 unit,
|
|
154 integration). The pre-existing Microsoft.OpenApi 2.0.0 vulnerability warning remains.
|
|
Delivery is restricted to Notification Service; Invemory producer/client migration and its
|
|
runtime validation remain separate pending work. TeamCity main triggers include general
|
|
checks, image publication, Contracts publication and production deployment. The owner
|
|
explicitly authorized this automatic production consequence for the Notification Service
|
|
delivery on 2026-10-24. It does not authorize delivery of the remaining Invemory repositories.
|