Skip to content

Commit 2236a4e

Browse files
authored
feat(ux): client-side connection error modal with algorithm debugging (#476)
* feat(ux): client-side connection error modal with algorithm debugging Replace server-side HTML error pages with rich client-side error modals. When SSH connections fail, the client now receives structured error data via the 'connection-error' WebSocket event and displays a helpful modal with algorithm debugging information. Changes: - Add SSH algorithm capture from ssh2 debug logs during handshake - Add algorithm analyzer to detect client/server mismatches and suggest fixes - Add error normalizer for consistent, meaningful SSH error messages - New 'connection-error' socket event with ConnectionErrorData payload - Remove server-side HTML error page rendering (error-page.ts deleted) - SSH validation moved from HTTP request to WebSocket connection - Update webssh2_client to v3.2.0 (includes ConnectionErrorModal) New files: - app/services/ssh/algorithm-capture.ts - Captures algorithm negotiation - app/services/ssh/algorithm-analyzer.ts - Analyzes compatibility issues - app/services/ssh/error-normalizer.ts - Normalizes SSH2 error messages - app/constants/algorithm-env-vars.ts - Environment variable mappings BREAKING CHANGE: Error responses are now JSON-only. Clients must handle the 'connection-error' event to display connection failures. * test: update e2e tests for client-side error modal Update authentication tests to work with the new ConnectionErrorModal: - Tests now click "Try Again" button to dismiss error modal before checking for login form visibility - Replace tests expecting HTTP 401/502 status codes with tests that verify error modal appears with appropriate messages - Remove unused testBasicAuthErrorResponse import The new behavior serves the client (200) for all Basic Auth requests and displays connection errors via WebSocket 'connection-error' event. * fix(lint): use export type...from syntax for re-exports Replace import-then-export pattern with direct export type...from syntax to fix SonarQube S7763 warnings in auth-utils.ts. * refactor(test): extract shared algorithm test fixtures Move duplicated AlgorithmSet builders from algorithm-analyzer and algorithm-capture tests into shared fixtures file to reduce code duplication flagged by SonarQube. * fix(lint): remove deprecated eslint-env comments ESLint flat config no longer recognizes /* eslint-env */ comments. The existing /* global */ comments already declare the needed browser globals, making eslint-env redundant. * refactor(test): use credential builders and shared fixtures - Use validCredentials(), invalidCredentials(), credentialsWithHost(), and credentialsWithPort() helpers in Playwright tests - Extract shared algorithm test fixtures to reduce inline definitions - Use it.each pattern for parametrized test cases * fix(lint): resolve SonarQube warnings - S7763: use export...from for type re-exports - S6571: simplify Error | unknown to just unknown - S4624: extract nested template literals to variables - S4144: deduplicate identical createModernClientSet function
1 parent 0e1c280 commit 2236a4e

38 files changed

+2523
-1081
lines changed

DOCS/api/ROUTES.md

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ WebSSH2 provides several HTTP routes for different authentication methods and ut
2020
**Screenshots:**
2121

2222
Login Form:
23-
<img width="341" alt="Login Form" src="https://github.com/user-attachments/assets/829d1776-3bc5-4315-b0c6-9e96a648ce06">
23+
![Login Form](https://github.com/user-attachments/assets/829d1776-3bc5-4315-b0c6-9e96a648ce06)
2424

2525
Terminal Configuration:
26-
<img width="341" alt="Terminal Configuration" src="https://github.com/user-attachments/assets/bf60f5ba-7221-4177-8d64-946907aed5ff">
26+
![Terminal Configuration](https://github.com/user-attachments/assets/bf60f5ba-7221-4177-8d64-946907aed5ff)
2727

2828
### 2. `/ssh/host/:host` - HTTP Basic Auth ⚠️ **DEPRECATED**
2929

@@ -45,7 +45,7 @@ WebSSH2 validates SSH credentials immediately upon receiving HTTP Basic Auth cre
4545
**Expected Behavior:**
4646

4747
1. **URL without embedded credentials:**
48-
```
48+
```text
4949
http://localhost:2222/ssh/host/example.com
5050
```
5151
- Invalid credentials → 401 Unauthorized
@@ -54,7 +54,7 @@ WebSSH2 validates SSH credentials immediately upon receiving HTTP Basic Auth cre
5454
- Success
5555

5656
2. **URL with embedded credentials:**
57-
```
57+
```text
5858
http://user:pass@localhost:2222/ssh/host/example.com
5959
```
6060
- Browser always uses URL credentials
@@ -154,33 +154,33 @@ const response = await fetch('/ssh', {
154154

155155
All routes support the following query parameters:
156156

157-
| Parameter | Type | Default | Description |
158-
|-----------|------|---------|-------------|
159-
| `port` | integer | 22 | SSH port to connect to |
160-
| `sshterm` | string | xterm-color | Terminal type |
161-
| `header` | string | - | Header text override |
162-
| `headerBackground` | string | green | Header background color |
163-
| `headerStyle` | string | - | Additional inline styles (e.g., `color: red`) |
164-
| `env` | string | - | Comma-separated env pairs (e.g., `FOO:bar,BAR:baz`) |
157+
| Parameter | Type | Default | Description |
158+
| ------------------ | ------- | ----------- | ----------------------------------------------------- |
159+
| `port` | integer | 22 | SSH port to connect to |
160+
| `sshterm` | string | xterm-color | Terminal type |
161+
| `header` | string | - | Header text override |
162+
| `headerBackground` | string | green | Header background color |
163+
| `headerStyle` | string | - | Additional inline styles (e.g., `color: red`) |
164+
| `env` | string | - | Comma-separated env pairs (e.g., `FOO:bar,BAR:baz`) |
165165

166166
### Example with Query Parameters
167167

168-
```
168+
```text
169169
http://localhost:2222/ssh/host/example.com?port=2244&sshterm=xterm-256color&env=DEBUG:true,NODE_ENV:production
170170
```
171171

172172
## Response Codes
173173

174-
| Code | Description |
175-
|------|-------------|
176-
| 200 | Success |
177-
| 302 | Redirect (for reauth) |
178-
| 400 | Bad Request (invalid parameters) |
179-
| 401 | Unauthorized (authentication required or failed) |
180-
| 404 | Not Found (invalid route) |
181-
| 500 | Internal Server Error |
182-
| 502 | Bad Gateway (SSH server unreachable) |
183-
| 504 | Gateway Timeout (SSH connection timed out) |
174+
| Code | Description |
175+
| ---- | ------------------------------------------------- |
176+
| 200 | Success |
177+
| 302 | Redirect (for reauth) |
178+
| 400 | Bad Request (invalid parameters) |
179+
| 401 | Unauthorized (authentication required or failed) |
180+
| 404 | Not Found (invalid route) |
181+
| 500 | Internal Server Error |
182+
| 502 | Bad Gateway (SSH server unreachable) |
183+
| 504 | Gateway Timeout (SSH connection timed out) |
184184

185185
## Error Response Format
186186

DOCS/configuration/ENVIRONMENT-VARIABLES.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,6 @@ See [CONFIG-JSON.md](./CONFIG-JSON.md) for `config.json` examples and additional
387387

388388
| Variable | Type | Default | Description |
389389
|----------|------|---------|-------------|
390-
| `WEBSSH2_LOGGING_FORMAT` | string | `json` | Structured log output format (`json` only at present) |
391390
| `WEBSSH2_LOGGING_LEVEL` | string | `info` | Global minimum log level (`debug`, `info`, `warn`, `error`) |
392391
| `WEBSSH2_LOGGING_STDOUT_ENABLED` | boolean | `true` | Enables stdout transport when `true` |
393392
| `WEBSSH2_LOGGING_STDOUT_MIN_LEVEL` | string | `info` | Per-transport minimum level for stdout delivery |
@@ -469,6 +468,34 @@ docker run --name webssh2 --rm -it \
469468
| `WEBSSH2_SESSION_SECRET` | string | auto-generated | Session encryption secret |
470469
| `WEBSSH2_SESSION_NAME` | string | `webssh2.sid` | Session cookie name |
471470

471+
### SSO Configuration (Single Sign-On)
472+
473+
| Variable | Type | Default | Description |
474+
|----------|------|---------|-------------|
475+
| `WEBSSH2_SSO_ENABLED` | boolean | `false` | Enable SSO authentication via trusted headers |
476+
| `WEBSSH2_SSO_CSRF_PROTECTION` | boolean | `true` | Enable CSRF protection for SSO endpoints |
477+
| `WEBSSH2_SSO_TRUSTED_PROXIES` | array | `[]` | IP addresses/subnets of trusted reverse proxies (comma-separated or JSON) |
478+
| `WEBSSH2_SSO_HEADER_USERNAME` | string | `null` | HTTP header containing the username |
479+
| `WEBSSH2_SSO_HEADER_PASSWORD` | string | `null` | HTTP header containing the password |
480+
| `WEBSSH2_SSO_HEADER_SESSION` | string | `null` | HTTP header containing the session identifier |
481+
482+
#### SSO Example
483+
484+
```bash
485+
# Enable SSO with header-based authentication behind a reverse proxy
486+
WEBSSH2_SSO_ENABLED=true
487+
WEBSSH2_SSO_CSRF_PROTECTION=true
488+
WEBSSH2_SSO_TRUSTED_PROXIES="10.0.0.0/8,172.16.0.0/12"
489+
WEBSSH2_SSO_HEADER_USERNAME="X-Auth-Username"
490+
WEBSSH2_SSO_HEADER_PASSWORD="X-Auth-Password"
491+
```
492+
493+
#### SSO Security Notes
494+
495+
- **Trusted Proxies**: Only configure trusted proxy IPs/subnets. Requests from untrusted sources will have SSO headers ignored.
496+
- **CSRF Protection**: Keep enabled unless your reverse proxy handles CSRF protection.
497+
- **Header Security**: Ensure your reverse proxy strips incoming SSO headers from client requests to prevent header injection attacks.
498+
472499
## Data Type Formats
473500

474501
### Boolean Values

DOCS/features/AUTHENTICATION.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ POST authentication supports flexible parameter passing:
5252
- **Precedence:** Body parameters override query parameters
5353

5454
This allows SSO systems to:
55+
5556
1. Send credentials securely in POST body
5657
2. Specify target host via URL query parameters
5758
3. Mix and match based on security requirements
@@ -67,6 +68,7 @@ WebSSH2 validates SSH credentials **immediately** upon receiving HTTP Basic Auth
6768
**URL Format:** `http://localhost:2222/ssh/host/example.com`
6869

6970
**Flow:**
71+
7072
1. User navigates to URL
7173
2. Browser prompts for credentials (if not cached)
7274
3. WebSSH2 validates SSH credentials immediately
@@ -78,6 +80,7 @@ WebSSH2 validates SSH credentials **immediately** upon receiving HTTP Basic Auth
7880
**URL Format:** `http://user:pass@localhost:2222/ssh/host/example.com`
7981

8082
**Flow:**
83+
8184
1. Browser automatically uses embedded credentials
8285
2. WebSSH2 validates SSH credentials immediately
8386
3. **Invalid credentials** → 401 Unauthorized → **No re-authentication possible**
@@ -138,6 +141,7 @@ if (!validationResult.success) {
138141
- Re-authentication won't help
139142

140143
This approach:
144+
141145
- ✅ Prevents invalid credentials from reaching WebSocket layer
142146
- ✅ Provides immediate feedback on authentication failures
143147
- ✅ Follows HTTP standards for proper status codes
@@ -149,12 +153,15 @@ This approach:
149153
WebSSH2 uses content negotiation to provide appropriate error responses:
150154

151155
**Browser Requests** (`Accept: text/html`):
156+
152157
- Returns a styled HTML error page
153158
- Shows error title, message, and connection details
154159
- Includes "Try Again" button for 401 errors
155160

156161
**API Requests** (`Accept: application/json`):
162+
157163
- Returns JSON with error details:
164+
158165
```json
159166
{
160167
"error": "Authentication failed",
@@ -178,6 +185,7 @@ This ensures browsers display user-friendly error pages while API clients receiv
178185
### Migration Examples
179186

180187
#### Before (Basic Auth - Deprecated)
188+
181189
```bash
182190
# Browser-based
183191
curl -u "username:password" "http://localhost:2222/ssh/host/example.com"
@@ -187,6 +195,7 @@ curl "http://username:password@localhost:2222/ssh/host/example.com"
187195
```
188196

189197
#### After (POST Auth - Recommended)
198+
190199
```bash
191200
# Standard POST request - all parameters in body
192201
curl -X POST "http://localhost:2222/ssh" \
@@ -265,13 +274,15 @@ async function authenticateWithSSO(ssoToken, targetHost, targetPort = 22) {
265274
### For Users
266275

267276
1. **For interactive re-authentication:** Use URLs without embedded credentials
268-
```
277+
278+
```text
269279
✅ Good: http://localhost:2222/ssh/host/example.com
270280
❌ Problematic: http://user:pass@localhost:2222/ssh/host/example.com
271281
```
272282

273283
2. **For scripted/automated access:** Embed credentials in URL (no retry needed)
274-
```
284+
285+
```text
275286
✅ Good: http://validuser:validpass@localhost:2222/ssh/host/example.com
276287
```
277288

@@ -299,6 +310,7 @@ async function authenticateWithSSO(ssoToken, targetHost, targetPort = 22) {
299310
**Symptom:** 401 response but no authentication dialog appears
300311

301312
**Causes & Solutions:**
313+
302314
1. **Embedded credentials in URL** → Remove credentials from URL
303315
2. **Browser cached credentials** → Clear browser auth cache or use incognito mode
304316
3. **CORS issues** → Check server CORS configuration
@@ -310,6 +322,7 @@ async function authenticateWithSSO(ssoToken, targetHost, targetPort = 22) {
310322
**Solution:** Ensure scripts are properly encoding credentials and handling 401 responses
311323

312324
**Example curl command:**
325+
313326
```bash
314327
curl -u "username:password" "http://localhost:2222/ssh/host/example.com"
315328
```
@@ -328,7 +341,8 @@ curl -u "username:password" "http://localhost:2222/ssh/host/example.com"
328341
### Headers
329342

330343
**401 Response Headers:**
331-
```
344+
345+
```http
332346
HTTP/1.1 401 Unauthorized
333347
WWW-Authenticate: Basic realm="WebSSH2"
334348
```
@@ -342,6 +356,7 @@ DEBUG=webssh2:routes npm start
342356
```
343357

344358
Look for log entries like:
359+
345360
- `Validating SSH credentials for user@host:port`
346361
- `SSH validation successful for user@host:port`
347362
- `SSH validation failed for user@host:port`
@@ -350,4 +365,4 @@ Look for log entries like:
350365

351366
- [CONFIG.md](CONFIG.md) - Server configuration options
352367
- [SERVER_API.md](SERVER_API.md) - WebSocket API documentation
353-
- [DEVELOPMENT.md](DEVELOPMENT.md) - Development and testing guidelines
368+
- [DEVELOPMENT.md](DEVELOPMENT.md) - Development and testing guidelines

0 commit comments

Comments
 (0)