# Broker Authentication Configuration

Where MQTT authentication for the terminal fleet and for this app's own
central subscriber gets configured — and the current state of each, which
is: **neither is configured yet**. Every MQTT client today, device or
backend, connects to the broker anonymously.

**This is a deliberate, acknowledged decision, not an oversight.** Both
the MQTT client (the terminal fleet) and the server (`emuMQ`) are
momentpay's own — there is no third-party device or untrusted network
boundary in between today, and the broker isn't exposed beyond what
momentpay controls. Per-device authentication is planned, not urgent;
this document is the runbook for when that plan gets scheduled, not a
flag that something is currently broken or exploitable in the threat
model as it stands.

## Two separate authentication concerns

| | Who connects | Where auth would be enforced | Status |
|---|---|---|---|
| **Device auth** | Physical terminals (MF919/SR600/Kozen), one MQTT client per device, `clientid` = `serial_number` | emuMQ's authenticator chain | Not configured — anonymous |
| **Backend subscriber auth** | This app's own single shared client (`phoenix_client_tms_apps_prem`, `DaProductApp.MQTT.start_phoenix_client/0` / `PlatformCore.MQTT.start_client/1`) | Same chain, since one broker-wide authenticator applies to every connecting client, ours included | Not configured — anonymous |

They're listed separately because they get fixed with different schema/code
changes, but **they turn on together** — the moment any authenticator is
added to the chain, both device connections *and* this app's own
`Tortoise` client stop being able to connect until each presents valid
credentials. Enabling one without the other's fix ready will drop the
fleet or cut this app off from the broker.

## What looks like device auth today, but isn't

Two fields already exist that read like credentials and are not:

- **`product_key` / `product_secret`** (hardcoded defaults `"pFppbioOCKlo5c8E"` /
  `"sj2AJl102397fQAV"` in `parameter_push_service.ex`, `remote_log_service.ex`,
  `ota_configuration_controller.ex`) — these are topic-namespace segments
  (`/ota/:product_key/:serial_number/logpush`), not MQTT CONNECT
  credentials. The broker never checks them.
- **`ota_configurations.username`** (defaults to the literal string
  `"user001"` in `ota_service.ex`, `parameter_push_service.ex`) — this is a
  value *told to the device* inside its OTA config payload (some vendor
  SDKs expect a username field in provisioning JSON even when unused), not
  a value the broker verifies against anything.

Neither maps to a real credential store. Building device auth means adding
one, not wiring up what's already there.

## 1. Device authentication — where to configure it

### Schema: add a credential to `tms_terminals`

`tms_terminals` (`apps/da_product_app/priv/repo/migrations/20240101000009_create_tms_terminals.exs`)
has `serial_number`, `imei`, `mac`, `pubkey`, `appkey` and others, but no
field meant to hold an MQTT secret — `pubkey`/`appkey` are values *read
from* device status logs (encryption/signing material the terminal
reports), not something we assign. A new migration is needed:

```elixir
# apps/da_product_app/priv/repo/migrations/<timestamp>_add_mqtt_credential_to_tms_terminals.exs
add :mqtt_password_hash, :string   # bcrypt/sha256, never the plaintext
```

Generate a random per-device secret at provisioning time (whenever a
terminal row is first created), store only its hash, and hand the
plaintext to the device once through whatever channel actually provisions
it (QR code, config file, vendor's own provisioning flow) — the same
"only shown once" principle `BrokerLive.Settings` already uses for API
keys.

### emuMQ: the `mysql` authenticator

Configured **on the broker**, not in this app — `BrokerLive.Auth`
(`/admin/broker/auth`) shows the authenticator chain and lets you reorder
it and watch its health, but deliberately doesn't expose creating one
(see that page's moduledoc: backend config for an authenticator can
include connection credentials, and this app treats that as a
one-time-setup action, not a business-backoffice form). Create it once,
either via the standalone dashboard's Authentication page or the API
directly:

```
POST /api/v5/authentication
{
  "mechanism": "password_based",
  "backend": "mysql",
  "server": "<mysql-host>:3306",
  "database": "shukria_transactions",
  "username": "<read-only-mysql-user>",
  "password": "<...>",
  "password_hash_algorithm": {"name": "sha256"},
  "query": "SELECT mqtt_password_hash AS password_hash FROM tms_terminals WHERE serial_number = ${username}"
}
```

This makes `clientid`/username on the MQTT CONNECT packet equal to
`serial_number`, checked against the hash column above. Use a read-only
MySQL user scoped to `SELECT` on `tms_terminals` alone — this credential
lives in emuMQ's own config, not in `tmsuat_apps` at all.

Once created, `/admin/broker/auth` is where you'd verify it's healthy
(green "Connected" badge) and see it take its place in the chain — that
page is the ongoing-visibility half of this, not the setup half.

### ACL: what a device may publish/subscribe to

Authentication answers "is this a real terminal"; authorization answers
"can *this* terminal touch *that* topic" — without it, an authenticated
device could still subscribe to another merchant's `tms/status/+`. Same
pattern as authentication: configure a `mysql` authorization source (also
visible/reorderable at `/admin/broker/auth`, second table on that page)
with a query scoping each `serial_number` to its own topic namespace,
e.g. `tms/status/${clientid}`, `ota/${clientid}/#`.

## 2. Backend subscriber authentication — where to configure it

### Broker side

Create one dedicated API-style MQTT credential for this app's own
client — **do not** reuse device credentials or the `EMQX_API_KEY`/
`EMQX_API_SECRET` pair `PlatformCore.Emqx` uses (that pair authenticates
HTTP calls to the broker's REST API; this is a separate MQTT CONNECT
credential entirely). Add it as a row the `mysql` authenticator's query
can match, or — simpler, since there's exactly one such client — add it
to EMQX's `built_in_database` authenticator instead of overloading the
`mysql` one with a single non-terminal row:

```
POST /api/v5/authentication/{mysql-authenticator-id}/users
```
(or, in the dashboard, add a second authenticator of type
`built_in_database` ordered *before* the `mysql` one, with just this one
user in it — keeps the terminal-facing query untouched by the backend's
own credential).

### App side: two changes, neither made yet

1. **Config** — `config/config.exs` (`mqtt_host`/`mqtt_port`, currently
   `apps/da_product_app` only) needs `mqtt_username`/`mqtt_password`
   alongside them, sourced from `System.get_env` in
   `config/runtime.exs` exactly like `EMQX_API_KEY`/`EMQX_API_SECRET` are
   — never a literal in `config.exs`.

2. **Code** — `apps/da_product_app/lib/da_product_app/mqtt.ex` and
   `apps/platform_core/lib/platform_core/mqtt.ex` both call
   `Tortoise.Supervisor.start_child/1` with a `server:` tuple and no
   `user_name`/`password` opts. Tortoise takes those as top-level keys
   alongside `client_id`/`server`:

   ```elixir
   Tortoise.Supervisor.start_child(
     client_id: client_id,
     server: server_opts,
     user_name: Application.get_env(:da_product_app, :mqtt_username),
     password: Application.get_env(:da_product_app, :mqtt_password),
     handler: ...,
     subscriptions: ...
   )
   ```

   Both files currently omit these entirely — that's why anonymous access
   still works even after `config.exs` was pointed at `localhost` instead
   of `demo.ctrmv.com` (a host change, not an auth change).

## Sequencing, since both switch on at once

1. Add the `mqtt_password_hash` migration and backfill a secret per
   existing terminal (or leave new terminals to get one at creation and
   accept old ones stay unauthenticated until their next provisioning
   cycle — a product decision, not a technical one).
2. Add `mqtt_username`/`mqtt_password` config + the `Tortoise` opts above,
   deploy, confirm the backend's client still connects (it will — no
   authenticator exists yet, so credentials are accepted or ignored
   either way).
3. Create the broker-side credential for the backend client (step 2,
   broker side, above).
4. Create the `mysql` authenticator and authorization source.
5. Watch `/admin/broker/auth` — both the authenticator and the backend's
   own client should show connected/healthy. Watch `/admin/broker/clients`
   for the fleet reconnecting with real credentials as each terminal's
   next provisioning cycle picks up its assigned secret.

Only after step 3 succeeds should step 4 go live — reversing that order
authenticates terminals before the backend itself has a credential to
connect with, and the backend's own subscriber (and with it,
`TerminalEventListener`'s real-time status feed) goes dark.
