Message Flows
This page traces the two critical request paths through the system step by step. Read these carefully — they show exactly what every module does and why it exists.
Flow A — Account Linking (OAuth2)
Account Linking happens once, when a user enables the Alexa Skill for the first time. The goal: exchange a username/password for a pair of JWT tokens that Alexa will attach to every future directive.
Phase status
The OAuth2 server on the home server (right half of the diagram) is fully implemented (Phase 4). The OAuth proxy (left half) runs on a Lambda Function URL (Phase 5). During development you can test OAuth directly at http://localhost:8080/oauth/....
Key security mechanisms
| Mechanism | What it does |
|---|---|
| PKCE (S256) | Alexa sends a code_challenge (SHA-256 hash of code_verifier) during authorize. At token exchange it sends the raw code_verifier. The server recomputes the hash and compares — ensures only the original caller can exchange the code. |
| Claims validated before consume | At token exchange the redirect_uri and client_id are checked against the stored entry, then PKCE is verified, and only then is auth_codes.redeem(code) called. A bad redirect_uri/client_id therefore cannot consume the code and lock out the legitimate client. |
| Auth code is single-use | auth_codes.redeem() atomically consumes the entry. A replayed code returns invalid_grant. |
| Refresh token rotation | On every /oauth/token?grant_type=refresh_token, the old token is atomically consumed (pop_refresh_token) before the new pair is issued. |
bcrypt (via PasswordHasherPort) | Passwords are never stored in plain text — only the hash. Verification runs in a thread executor (bcrypt is CPU-bound) and checks unknown users against a dummy hash so response timing does not reveal whether a username exists. |
| Rate limiting | POST /oauth/authorize is throttled per-IP and per-IP:username (HTTP 429); POST /oauth/token is throttled per-IP. |
| JWT expiry | Access tokens expire after 60 minutes (configurable via JwtService). Alexa uses the refresh token to get new ones automatically. |
Flow B — Voice Command
This is the hot path — everything that happens between "Alexa, switch to ZDF" and the TV changing channel.
Phase status
The home server portion (FastAPI → Router → Handler → Command → Adapter → Device) is fully implemented (Phase 3 + 2). The Lambda proxy + S3 beacon (the top section) is planned (Phase 5). During development, POST directly to http://localhost:8080/alexa/directive.
What each layer does in this flow
| Layer | Responsibility in this flow |
|---|---|
| Lambda | Looks up current home server URL (S3 beacon); signs the request with X-Tiberio-Timestamp + X-Tiberio-Signature; forwards raw directive |
| FastAPI route | Validates the HMAC signature (when a shared secret is configured); extracts and validates the Bearer JWT via TokenValidatorPort (401 if invalid); enforces scope == "alexa" (403 if not) |
| AlexaDirectiveRouter | Parses the Alexa JSON into a typed model; dispatches to the correct handler by (namespace, name) |
| PowerHandler | Extracts endpoint ID and correlation token; calls TurnOnCommand/TurnOffCommand; builds the Alexa response |
| TurnOnCommand / TurnOffCommand | Resolve the device and its PowerablePort adapter via _find_and_resolve(endpoint_id, PowerablePort), then call adapter.turn_on(device) / adapter.turn_off(device); raise DeviceNotFoundError when the endpoint is unknown |
| DeviceRegistryPort | find_device(endpoint_id) returns the Device (e.g. a TvChannel) or None; the command raises DeviceNotFoundError(endpoint_id) on None |
HarmonyTvAdapter (PowerablePort) | turn_on(device): when isinstance(device, TvChannel) it runs ensure_activity(device.watch_activity) then set_channel(device.channel_number); otherwise a no-op. WebSocket calls to the Harmony Hub map Hub exceptions to DeviceUnavailableError |
Error handling
Every handler wraps the command call in a try/except block and maps domain errors to Alexa error responses:
| Exception | Alexa error type | Alexa behavior |
|---|---|---|
DeviceNotFoundError | NO_SUCH_ENDPOINT | "That device is not available" |
DeviceUnavailableError | ENDPOINT_UNREACHABLE | "That device is not responding" |
ValueError | VALUE_OUT_OF_RANGE | "That value is out of range" |
| Any other exception | INTERNAL_ERROR | "Sorry, something went wrong" |
Flow C — Token Refresh
Alexa automatically refreshes the access token when it expires (every 60 minutes). The refresh token rotates on each use.
Refresh token rotation means a stolen refresh token can only be used once. The atomic pop_refresh_token (single check-and-revoke) also guarantees that two concurrent refresh requests with the same token cannot both succeed — the legitimate user's next refresh will fail, alerting them to the compromise.