- **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
Instantiates `DoorUnlockStack` with an explicit `stackName` of `seahaven-door-unlock-api`, pinned to account `328440206208` / `us-east-1`.
### `lib/door-unlock-stack.ts`
Defines every resource the stack owns:
- The four Lambda functions (Node 24.x, arm64, 60-day log retention), bundled from TypeScript with esbuild
- 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)
- The custom domain, ACM certificate import, and Route 53 A record for `doorunlock.seahaven.com`
- Imports of the SSM parameters, the phone-password secret, and the `site-alerts` SNS topic, with the corresponding `grantRead` IAM permissions
- The four per-Lambda CloudWatch error alarms
### `cdk.json`
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`.