You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(tunnel): auth expiry detection, in-app renewal, and token monitoring
Handle DevTunnel auth token expiry gracefully with proactive monitoring
and in-app renewal via device code flow.
Backend (src/tunnel/index.js):
- Detect auth errors ('login required', 'not logged in') in health check
- Auth-wait mode: poll every 30s, auto-reconnect when user re-auths
- Check isLoggedIn() as fallback for 'Tunnel not found' errors
- Token lifetime monitoring: emit auth-expiring when < 1h remaining
- Prefer Entra login for new sessions (auto-refreshes for weeks via MSAL)
- Warn when logged in with GitHub (8h token limit)
- Export parseLoginInfo() and getLoginInfo() for routes
- Remove unref() from health check interval (unreliable in PM2)
Backend (src/server/index.js):
- Wire tunnel events before startTunnel() (fixes missed connected event)
- Broadcast tunnel-status WebSocket messages to all clients
- Push notifications on auth-expiring and auth-expired
- Track tunnelStatus in server state for API endpoint
- Detect auth-expired on startup when tunnel fails to start
Backend (src/server/routes.js):
- GET /api/tunnel/status: returns tunnel state, provider, token lifetime
- POST /api/tunnel/renew: spawns devtunnel user login -d, parses device
code and URL from output, returns { url, code } as JSON
Frontend:
- TunnelBanner component with shared Zustand store (tunnelStore.ts)
- States: expiring, expired, renewing (shows code + copy/open), renewed, failed
- All states have dismiss (✕) button
- Renew button on all states (user picks their account on auth page)
- 20s fetch timeout for renew endpoint (devtunnel takes up to 15s)
- WebSocket tunnel-status forwarding in useTerminalSocket
- WSTunnelStatusMessage type added to WebSocket protocol
- fetchTunnelStatus() and renewTunnelAuth() API functions
Tests:
- 22 new tests: parseLoginInfo, device code regex, event contracts,
API endpoints, WebSocket broadcast patterns
- Updated server.test.js tunnel mock with new exports
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|`url`| string | Device login URL the user should open |
1015
+
|`code`| string | One-time code the user enters at the device URL |
1016
+
1017
+
**Response (504):**
1018
+
1019
+
```json
1020
+
{ "error": "Timed out waiting for device code" }
1021
+
```
1022
+
1023
+
Returned when the device code flow does not complete within the expected timeout.
1024
+
1025
+
---
1026
+
978
1027
### Port Preview
979
1028
980
1029
#### `GET /preview/:port/*`
@@ -1140,6 +1189,25 @@ Sent during an in-app update (triggered via `POST /api/update`). Allows the fron
1140
1189
1141
1190
The `status` field follows the same values as `GET /api/update/status`. When `status` reaches `restarting`, the WebSocket connection will close shortly after (close code 1012 for non-PM2 installs).
1142
1191
1192
+
#### Tunnel Status
1193
+
1194
+
Broadcast when the tunnel connection state changes. Allows the frontend to show tunnel health and prompt for re-authentication when tokens expire.
Copy file name to clipboardExpand all lines: docs/architecture.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -118,8 +118,12 @@ Manages Azure DevTunnel lifecycle: login, create, host, cleanup. Includes a **wa
118
118
-**Zombie detection** — if host connections drop to 0 for two consecutive checks (60s grace), the stale process is killed and a restart is initiated.
119
119
-**Crash detection** — an `exit` handler on the child process triggers immediate restart if the process dies.
120
120
-**Auto-restart** — exponential backoff (1s → 2s → 5s → 10s → 15s → 30s), up to 10 attempts before giving up.
121
+
-**Auth-wait system** — detects auth token expiry (Microsoft limitation), enters an auth-wait mode, polls for re-authentication via device code flow, and auto-reconnects once a fresh token is obtained.
122
+
-**Token lifetime monitoring** — tracks the remaining lifetime of the DevTunnel auth token and emits warnings when less than 1 hour remains, giving the frontend time to prompt the user.
121
123
-**Event emitter** — exports `tunnelEvents` (EventEmitter) with events: `connected`, `disconnected`, `reconnecting`, `failed`. The server subscribes for logging.
122
124
125
+
Also exports `getLoginInfo()` (returns current auth provider and token expiry) and `parseLoginInfo()` (parses raw `devtunnel` CLI output into structured login metadata).
126
+
123
127
### `tunnel/install.js` — DevTunnel Installer
124
128
125
129
Handles automatic installation of the DevTunnel CLI when it's not found on the system. Prompts the user interactively and installs via the appropriate package manager (brew on macOS, curl on Linux, winget on Windows). Used by `server.js` during startup when tunnel mode is enabled.
Copy file name to clipboardExpand all lines: docs/security.md
+7Lines changed: 7 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -176,6 +176,13 @@ The following UI features are entirely client-side and introduce **no new server
176
176
- Use `--lan` or `--host 0.0.0.0` to allow LAN access
177
177
- The tunnel feature handles TLS via Azure DevTunnels
178
178
179
+
### Tunnel Token Expiry
180
+
181
+
- DevTunnel auth tokens expire periodically (a Microsoft-imposed limitation)
182
+
- TermBeam detects token expiry and enters **auth-wait mode**, pausing tunnel operations until a fresh token is obtained
183
+
- Users can renew the token in-app via a device code flow (`POST /api/tunnel/renew`), which returns a URL and one-time code to complete re-authentication
184
+
- Once renewed, the tunnel reconnects automatically — no server restart required
0 commit comments