- **Token authorizer Lambda** — a REQUEST-type Lambda authorizer validates the `?token=` query-string value (the same shared secret the phones already send) against the `/seahaven/door-unlock/auth-token` SSM parameter, so unauthenticated callers are rejected at the gateway (401/403) before any handler runs. Identity source is `$request.querystring.token`; results are cached 5 minutes. Fail-closed. The handlers also re-validate the token as defense-in-depth.
- **CloudWatch alarms** — one error alarm per Lambda (unlock, lockdown, authorizer, poller); each fires on `Errors > 0` and notifies the cross-stack `site-alerts` SNS topic (ALARM state only)
## Infrastructure (CDK)
All infrastructure is defined as code with the **AWS CDK v2 (TypeScript)**; `aws-cdk-lib` is pinned to `2.261.0`. The whole system is a single CloudFormation stack.
### Layout
```
bin/app.ts # CDK app entry point
lib/door-unlock-stack.ts # DoorUnlockStack — all resource definitions
- The HTTP API (`door-unlock-api`), its `GET /unlock`, `GET /lockdown`, and `GET /lockdown/status` routes, throttling, and JSON access logging
- The `HttpLambdaAuthorizer` token authorizer (identity source `$request.querystring.token`, 5-minute result cache)
- The EventBridge rule that invokes the poller once a minute, plus the poller's VPC config and security group (imported VPC/subnets, egress to the Elements API and phone LAN)
- Imports of the SSM parameters, the phone-password secret, the afterhours 3CX XAPI secrets, and the `site-alerts` SNS topic, with the corresponding `grantRead` IAM permissions
CDK configuration committed to the repo. The `app` command runs `npx tsx bin/app.ts`, so the TypeScript entry point executes directly via `tsx` (no separate compile step). It also carries the `watch` include/exclude globs and the CDK feature-flag `context`.
### Commands
```bash
npx cdk synth # synthesize the CloudFormation template
npx cdk diff # diff against the deployed stack
npx cdk deploy # deploy (see Manual Deployment below)
```
The same commands are also exposed as npm scripts (`npm run synth`, `npm run diff`, `npm run deploy`).
The canonical map of Sea Haven's AWS infrastructure lives in Confluence. This project's `seahaven-door-unlock-api` stack is represented there as a Mermaid subgraph.
- **[AWS Architecture Map](https://seahaven.atlassian.net/wiki/spaces/IT/pages/1540098)** (Confluence, IT space, page 1540098)
| Bohemia - Whole Building | `4b4a3e6b-c903-4cce-8cd6-288612bf0542` | 3 |
| Ronkonkoma - Whole Building | `ff9876bc-c54f-472e-aef9-d2bffd4b7cf7` | 4 |
Pressing the line key toggles the lockdown on/off and displays the current status on the phone screen.
**Known limitation:** Line key LED color does not currently change to reflect lockdown status. The T58W's XML Browser key type (17) does not support persistent LED color changes via Push XML or Execute commands — LED commands are transient and immediately overridden by the phone's key type management.
- **`.github/workflows/ci.yaml`** — on pull requests to `main`, runs the `ci-typescript-cdk` reusable workflow (build, lint, synth).
- **`.github/workflows/deploy.yaml`** — on push to `main`, runs the `cd-cdk` reusable workflow which assumes the `githubdeploy-seahaven-door-unlock-api` OIDC role (`AWS_DEPLOY_ROLE_ARN` repo secret) and runs `cdk deploy`.
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.
| Template | Reserved (never write) | Sync-owned parking | Own line | Managed department BLFs | Personal |
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).