provisioning: phones, /prov with Basic auth, rate limit and IP block
/api/devices CRUD (MAC in any format → 12 lowercase hex; a 20-char provisioning password, stored encrypted and shown to admins, who type it into the phone) and GET /:id/config-preview with passwords masked. GET /prov/... per 07 §2: <mac>.cfg (Yealink), cfg<mac>.xml (Grandstream), the empty Yealink common file. Basic auth with the MAC and the device's password (timing-safe), 404 for unknown MACs or the other vendor's file, 403 outside the local nets when LAN-only, 404 when provisioning is off, Cache-Control no-store, last provisioned time and IP recorded. Limits: 20 requests / 10 min per IP outside the local nets (an office's phones rebooting together would pass it), and 10 failures block the IP for an hour. A first request without credentials isn't a failure: phones ask that way and retry after the 401. Templates (pure functions of a ProvisionContext) hold the account, voicemail, pickup codes and the provisioning settings; BLF, time zone and phonebook come with M10-T2. Web: Phones page (table, drawer with what to type into the phone, config preview; manual settings for generic phones). e2e m10-t1: phone added in the UI, 401 then the file, and a baresip phone registers with only the account from it. Also: the loadSnapshot test made its database inside the test (20 s); under a full parallel run that timed out now and then. It now does it in a hook like the others. Task: M10-T1
This commit is contained in:
1 parent
dc79120de0
commit
fa63908261
22 files changed
+1266
-43
No files matched your search
@@ -648,7 +648,6 @@ COPY --from=build /out/node_modules ./node_modules
|
||||
COPY --from=build /src/apps/api/dist ./dist
|
||||
COPY --from=build /src/apps/web/dist ./public
|
||||
COPY --from=build /src/apps/api/src/db/migrations ./dist/db/migrations
|
||||
# ponytail: provisioning templates (M10) get their COPY line when they exist.
|
||||
ENV NODE_ENV=production
|
||||
ENTRYPOINT ["/usr/bin/tini","--"]
|
||||
CMD ["node","dist/index.js"]
|
||||
|
||||
@@ -386,7 +386,7 @@ Admin resources (all `requireAdmin`; standard CRUD = `GET /x`, `POST /x`, `GET /
|
||||
| `/api/paging-groups` | members are set via `memberIds: [uuid]` in POST/PATCH (no order). Not a destination, so delete never returns 409 |
|
||||
| `/api/sounds` | upload is `POST /api/sounds?name=&filename=` with the raw file as the body (like greetings, no multipart), `GET /:id/audio`, `PATCH /:id {name}`, `DELETE /:id` (409 with `usedBy`: IVR greeting/invalid sound, announcements, the recording notice), `POST /tts` (M12). M6 lists prompts only; MOH classes and files come with M8 |
|
||||
| `/api/moh-classes` | `POST {name}` (slug, not `default`), `DELETE /:id` (409 with `usedBy` while an extension or queue uses it), `POST /:id/files?name=&filename=` (raw file body, like sounds), `GET /:id/files/:fileId/audio`, `DELETE /:id/files/:fileId`. No rename: the name is the folder and is stored on extensions and queues. Extensions and queues may only pick `default` or an existing class (400) |
|
||||
| `/api/devices` | `GET /:id/config-preview` (rendered provisioning file, passwords masked) |
|
||||
| `/api/devices` | `GET /:id/config-preview` (rendered provisioning file, passwords masked). As built (M10-T1): body `{mac (any format), vendor, model, label, extensionId}`; the DTO carries `provisioningUrl` and the `provisioningPassword` (admins type it into the phone); MAC taken → 409; preview of a generic phone → 400 |
|
||||
| `/api/blocked-numbers` | |
|
||||
| `/api/contacts` | company phonebook; `POST /import` CSV (`name,company,email,label1,number1,...`) |
|
||||
| `/api/webhooks` | `POST /:id/test`, `GET /:id/deliveries` |
|
||||
|
||||
@@ -40,7 +40,7 @@ Left sidebar navigation, in this order:
|
||||
| Conferences | `/admin/conferences` | number, name, PIN, admin PIN, max members, record. As built (M8-T3): admins see the PINs in the list (they hand them out); an admin PIN needs a different room PIN |
|
||||
| Paging | `/admin/paging` | number, name, members, duplex. As built (M8-T4): members are checkboxes (no order); the empty state explains `*80<ext>` |
|
||||
| Sounds & music | `/admin/sounds` | Prompts list with play buttons, upload (drag-drop), TTS (M12). MOH classes with their files (M8). As built (M8-T5): a **Music on hold** section: make a set (slug name), upload, play and remove its files, delete it. The user drawer's Phone tab and the queue form pick a set from a select. An **Announcements** section (play a sound, then a destination) lives here too: no page of its own (M6-T5) |
|
||||
| Phones (devices) | `/admin/devices` | Table: MAC, vendor, model, user, last provisioned. Form: MAC (any format → normalized), vendor, model, extension. Shows the provisioning URL to type into the phone, and "Preview config" |
|
||||
| Phones (devices) | `/admin/devices` | Table: MAC, vendor, model, user, last provisioned. Form: MAC (any format → normalized), vendor, model, extension. Shows the provisioning URL to type into the phone, and "Preview config". As built (M10-T1): the drawer shows URL, user name (MAC) and password; a generic phone shows the manual settings instead (07 §4); Grandstream gets a note that line keys aren't set |
|
||||
| Contacts | `/admin/contacts` | Company phonebook, CSV import |
|
||||
| Call history | `/admin/calls` | Filters + table + CSV export; a row expands to show the CDR legs and plays the recording |
|
||||
| Recordings | `/admin/recordings` | Searchable list, play, download, delete |
|
||||
|
||||
@@ -46,6 +46,21 @@ Rules:
|
||||
- On success: update `last_provisioned_at`, `last_provisioned_ip`. Set `Cache-Control: no-store`.
|
||||
- Never serve over plain HTTP. The reverse proxy only listens on 443. (Old phones that can't do modern TLS are not supported.)
|
||||
|
||||
As built (M10-T1), `routes/provisioning.ts` + `provisioning/limiter.ts`:
|
||||
- A request **without** an `Authorization` header gets the 401 but isn't a failure: phones ask without
|
||||
credentials first and retry after the challenge. Failures are unknown MACs (or a file for another
|
||||
vendor) and wrong credentials; 10 within an hour block the IP for an hour (429).
|
||||
- The 20 requests / 10 min limit applies to clients **outside** `network.localNets`: an office's
|
||||
phones rebooting together behind one address would pass it in a minute. Failures count for all.
|
||||
- The Basic auth user name is the 12-hex MAC (either case); it can't carry colons (Basic auth splits
|
||||
at the first one). The Yealink common file `y0000000000xx.cfg` is served without auth (it's empty).
|
||||
- Unrecognized file names (phones ask for several) are a plain 404 and not a failure.
|
||||
- Desk phones are provisioned for UDP on `sip.udpPort`. Templates in M10-T1 hold the account,
|
||||
voicemail, pickup codes and the provisioning settings; BLF keys, time zone and the phonebook come
|
||||
with M10-T2. `scripts/e2e/m10-t1.mjs` registers a baresip phone with only the account from the
|
||||
served file.
|
||||
- Vendors offered: Yealink, Grandstream, generic (Polycom stays a stretch goal).
|
||||
|
||||
## 3. Templates
|
||||
|
||||
The key names below come from vendor documentation. Each one is marked
|
||||
|
||||
Reference in new issue
Block a user