diff --git a/README.md b/README.md index eb67e1c..beee480 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ AWS Lambda middleware that allows Yealink desk phones to unlock the front door and manage lockdown profiles via LenelS2 Elements. Target account is **seahaven-prod** (`011934824531`) under HCP Terraform workspace `seahaven-door-unlock-api-prod`. ``` -Yealink T54W/T58W → HTTPS GET (?token=) → API Gateway (token authorizer) → Lambda → LenelS2 Elements API +Yealink T54W/T57W/T58W → HTTPS GET (?token=) → API Gateway (token authorizer) → Lambda → LenelS2 Elements API ``` Phones keep `https://doorunlock.seahaven.com`. Cutover is a Route 53 A-record flip in the mgmt zone (`Z06652411XKH89KTZD3XA`). DNS is not managed in this Terraform. @@ -97,7 +97,7 @@ Do not run `cdk deploy` against prod. The GitHub CDK deploy workflow is frozen. ## Phone Configuration -Configure DSS keys on the Yealink T54W/T58W (via phone web UI or 3CX): +Configure DSS keys on the Yealink T54W/T57W/T58W (via phone web UI or 3CX): - **Key 2 — Unlock Door** - Type: URL @@ -115,14 +115,16 @@ Custom 3CX templates are included with door unlock and lockdown URLs hardcoded. |----------|-------|-------|----------|---------| | `yealinkT54W-door-unlock.ph.xml` | T54W | Unlock Door | Managed by 3CX BLF | Dim after 5 min, never sleep | | `yealinkT54W-door-unlock-with-sp.ph.xml` | T54W | Unlock Door | SP1-3 via BLF sync | Dim after 5 min, never sleep | +| `yealinkT57W-door-unlock-with-sp.ph.xml` | T57W | Unlock Door | SP1-3 via BLF sync | Dim after 5 min, never sleep | | `yealinkT58W-door-unlock.ph.xml` | T58W | Unlock Door | Lockdown Toggle (Bohemia/Ronkonkoma) | Default T58W display settings | -Department colleague BLFs are not encoded in these templates. A scheduled Lambda (`door-unlock-api-blf-sync`) writes each Yealink user's 3CX BLF list from that user's first non-DEFAULT 3CX department, excluding the phone's own extension. Extension 100 is always included, even when the XAPI Users list omits it. Unlock and lockdown URL keys stay hardcoded in the template. Shared parking on the T54W+SP template is written by the sync job as 3CX SharedParking BLFs. +Department colleague BLFs are not encoded in these templates. A scheduled Lambda (`door-unlock-api-blf-sync`) writes each Yealink user's 3CX BLF list from that user's first non-DEFAULT 3CX department, excluding the phone's own extension. Extension 100 is always included, even when the XAPI Users list omits it. Unlock and lockdown URL keys stay hardcoded in the template. Shared parking on the T54W+SP and T57W+SP templates is written by the sync job as 3CX SharedParking BLFs. | Template | Reserved (never write) | Sync-owned parking | Own line | Managed department BLFs | Personal | | --- | --- | --- | --- | --- | --- | | `yealinkT54W-door-unlock.ph.xml` | `blf2` | none | `blf1` | `blf3`–`blf12` | `blf13+` | | `yealinkT54W-door-unlock-with-sp.ph.xml` | `blf2` | `blf3`–`blf5` (SP1–SP3) | `blf1` | `blf6`–`blf15` | `blf16+` | +| `yealinkT57W-door-unlock-with-sp.ph.xml` | `blf2` | `blf3`–`blf5` (SP1–SP3) | `blf1` | `blf6`–`blf15` | `blf16+` | | `yealinkT58W-door-unlock.ph.xml` | `blf2`–`blf4` | none | `blf1` | `blf5`–`blf14` | `blf15+` | The job authenticates to 3CX XAPI with the existing `afterhours-shift-manager/3cx-*` Secrets Manager values. Invoke `door-unlock-api-blf-sync` with `DRY_RUN=true` for a proposed-XML log and no writes. Set `SMOKE_EXTENSION` to PATCH a single extension. The daily EventBridge rule runs at `09:00 UTC` (05:00 ET during EDT). diff --git a/RUNBOOK-token-rotation.md b/RUNBOOK-token-rotation.md index bb9c854..e959898 100644 --- a/RUNBOOK-token-rotation.md +++ b/RUNBOOK-token-rotation.md @@ -17,6 +17,7 @@ plaintext, in both the `/unlock` and `/lockdown` query strings, across: - `yealinkT58W-door-unlock.ph.xml` - `yealinkT54W-door-unlock.ph.xml` - `yealinkT54W-door-unlock-with-sp.ph.xml` +- `yealinkT57W-door-unlock-with-sp.ph.xml` The token is the exact secret the API Gateway authorizer (`lambda/authorizer/authorizer-handler.ts`) validates against SSM SecureString `/seahaven/door-unlock/auth-token` via `timingSafeEqual`. diff --git a/firmware/T5XW-96.87.0.22.rom b/firmware/T5XW-96.87.0.22.rom new file mode 100644 index 0000000..8306e02 Binary files /dev/null and b/firmware/T5XW-96.87.0.22.rom differ diff --git a/lambda/blf-sync/blf-sync.test.ts b/lambda/blf-sync/blf-sync.test.ts index 11764bb..3fb2edf 100644 --- a/lambda/blf-sync/blf-sync.test.ts +++ b/lambda/blf-sync/blf-sync.test.ts @@ -19,9 +19,21 @@ test("resolveTemplateId matches longest Sea Haven template first", () => { resolveTemplateId("yealinkT54W-door-unlock-with-sp.ph.xml"), "t54w-door-unlock-with-sp" ); + assert.equal( + resolveTemplateId("yealinkT57W-door-unlock-with-sp.ph.xml"), + "t57w-door-unlock-with-sp" + ); assert.equal(resolveTemplateId("yealinkT54W-door-unlock.ph.xml"), "t54w-door-unlock"); assert.equal(resolveTemplateId("yealinkT58W-door-unlock.ph.xml"), "t58w-door-unlock"); assert.equal(resolveTemplateId("yealinkT54W.ph.xml"), undefined); + assert.equal(resolveTemplateId("yealinkT57W.ph.xml"), undefined); +}); + +test("T57W+SP slot contract matches T54W+SP", () => { + assert.deepEqual( + SLOT_CONTRACT["t57w-door-unlock-with-sp"], + SLOT_CONTRACT["t54w-door-unlock-with-sp"] + ); }); test("parseBlfs reads Line self-close and extension BLFs", () => { diff --git a/lambda/blf-sync/slot-contract.ts b/lambda/blf-sync/slot-contract.ts index 2a13abb..f26b039 100644 --- a/lambda/blf-sync/slot-contract.ts +++ b/lambda/blf-sync/slot-contract.ts @@ -1,6 +1,7 @@ export type TemplateId = | "t54w-door-unlock" | "t54w-door-unlock-with-sp" + | "t57w-door-unlock-with-sp" | "t58w-door-unlock"; export interface SharedParkingSlot { @@ -35,6 +36,17 @@ export const SLOT_CONTRACT: Record = { managedStart: 6, managedEnd: 15, }, + "t57w-door-unlock-with-sp": { + reserved: [2], + sharedParking: [ + { blfNo: 3, value: "SP1" }, + { blfNo: 4, value: "SP2" }, + { blfNo: 5, value: "SP3" }, + ], + ownLine: 1, + managedStart: 6, + managedEnd: 15, + }, "t58w-door-unlock": { reserved: [2, 3, 4], sharedParking: [], @@ -45,6 +57,7 @@ export const SLOT_CONTRACT: Record = { }; const TEMPLATE_MATCHERS: { id: TemplateId; needle: string }[] = [ + { id: "t57w-door-unlock-with-sp", needle: "yealinkT57W-door-unlock-with-sp" }, { id: "t54w-door-unlock-with-sp", needle: "yealinkT54W-door-unlock-with-sp" }, { id: "t54w-door-unlock", needle: "yealinkT54W-door-unlock" }, { id: "t58w-door-unlock", needle: "yealinkT58W-door-unlock" }, diff --git a/yealinkT57W-door-unlock-with-sp.ph.xml b/yealinkT57W-door-unlock-with-sp.ph.xml new file mode 100644 index 0000000..f630aab --- /dev/null +++ b/yealinkT57W-door-unlock-with-sp.ph.xml @@ -0,0 +1,22131 @@ + + +
+ phone-template + 150009 + + Yealink T57W - Door Unlock with SP + https://www.3cx.com/sip-phones/yealink-t5-series/ + preferred + + Yealink T57W + + Yealink T57W - Custom template with Door Unlock key and shared parking + + BLF + + + check-sync;reboot=true + + + check-sync;reboot=false + + + check-sync;reboot=false + + 1 + 1 + + + + + + https://%%PHONE_IP%%/api/auth/login?@admin:%%PHONE_WEB_PASSWORD%% + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + 0 + 1 + + + + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + 0 + 1 + + + + + 1 + %%blfno%% + 16 + %%param::pickup%% + + fnc=sd+blf+cp; + + + 1 + + 15 + %NULL% + + + + 1 + %%blfno%% + 13 + %NULL% + + + + 1 + %%blfno%% + 13 + %NULL% + + + + 1 + %%blfno%% + 13 + %NULL% + + + + 1 + %%blfno%% + 13 + %NULL% + + + + 1 + %%blfno%% + 10 + %NULL% + + + + + + phone + + Yealink T57W - Door Unlock with SP + + + + + + + + + phone + + Yealink T4x Identity + + + +
\ No newline at end of file