mirror of https://github.com/jetkvm/kvm.git
2593 lines
67 KiB
Markdown
2593 lines
67 KiB
Markdown
# JetKVM Multi-Session Support
|
|
|
|
## Table of Contents
|
|
|
|
1. [Overview](#overview)
|
|
2. [Session Modes](#session-modes)
|
|
3. [Session Settings](#session-settings)
|
|
4. [Session Lifecycle](#session-lifecycle)
|
|
5. [Grace Period & Reconnection](#grace-period--reconnection)
|
|
6. [Session Approval System](#session-approval-system)
|
|
7. [Primary Role Transfer](#primary-role-transfer)
|
|
8. [Emergency Promotion](#emergency-promotion)
|
|
9. [Permissions System](#permissions-system)
|
|
10. [Session Identification](#session-identification)
|
|
11. [Testing Guide](#testing-guide)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
JetKVM supports multiple concurrent connections to a single device, allowing multiple users to collaborate or observe remote system management. The multi-session system uses a role-based access control model with automatic session management and conflict resolution.
|
|
|
|
### Key Features
|
|
|
|
- **Multiple concurrent connections** - Up to 10 simultaneous sessions per device
|
|
- **Role-based permissions** - Four distinct session modes with different privilege levels
|
|
- **Automatic session promotion** - Seamless handoff between primary sessions
|
|
- **Grace period reconnection** - Recover from accidental disconnects without losing control
|
|
- **Session approval workflow** - Optional gating for new connections
|
|
- **Transfer protection** - Prevent unwanted session takeovers
|
|
- **Automatic nickname generation** - Identify sessions by browser type
|
|
|
|
### Design Philosophy
|
|
|
|
The multi-session system is designed with the following principles:
|
|
|
|
1. **Always have a primary** - The system ensures at least one session has control when sessions exist
|
|
2. **Graceful degradation** - Accidental disconnects are protected by grace periods
|
|
3. **Manual control prioritized** - User-initiated actions take precedence over automatic promotion
|
|
4. **Security by default** - Emergency promotions use trust scoring to select the most appropriate session
|
|
|
|
---
|
|
|
|
## Session Modes
|
|
|
|
Every session operates in one of four modes, each with different permissions and responsibilities.
|
|
|
|
### Primary Mode
|
|
|
|
![Primary Session Screenshot - placeholder for image]
|
|
|
|
**Permissions**: Full control of the remote system
|
|
|
|
The primary session has complete control over the KVM device and can:
|
|
- View video feed
|
|
- Send keyboard and mouse input
|
|
- Control power (ATX/DC)
|
|
- Mount/unmount virtual media
|
|
- Access all system settings
|
|
- Manage other sessions (approve, deny, transfer, kick)
|
|
- Access serial console and terminal
|
|
- Configure USB devices and extensions
|
|
|
|
**Visual Indicators**:
|
|
- Primary badge displayed in session list
|
|
- Full control UI enabled
|
|
- All menu options accessible
|
|
|
|
**Automatic Behaviors**:
|
|
- Only one primary session exists at any time
|
|
- Primary session receives exclusive input control
|
|
- Times out after 5 minutes of inactivity (configurable)
|
|
- Can voluntarily release control to observers
|
|
|
|
### Observer Mode
|
|
|
|
![Observer Session Screenshot - placeholder for image]
|
|
|
|
**Permissions**: View-only access with request capability
|
|
|
|
Observer sessions can:
|
|
- View video feed in real-time
|
|
- See mounted media (but cannot mount/unmount)
|
|
- Request primary control
|
|
- View session list
|
|
|
|
Observer sessions **cannot**:
|
|
- Send keyboard/mouse input
|
|
- Control power or hardware
|
|
- Modify system settings
|
|
- Manage other sessions
|
|
|
|
**Visual Indicators**:
|
|
- "Request Control" button visible
|
|
- Input controls disabled/grayed out
|
|
- Settings menu locked
|
|
|
|
**Automatic Behaviors**:
|
|
- Promoted to primary when no primary exists (if not blacklisted)
|
|
- Can be promoted by current primary via manual transfer
|
|
- Automatically becomes observer if primary is transferred away
|
|
|
|
### Queued Mode
|
|
|
|
![Queued Session Screenshot - placeholder for image]
|
|
|
|
**Permissions**: Same as observer, with pending request status
|
|
|
|
Queued sessions have:
|
|
- Same permissions as observers
|
|
- Visible indication that control has been requested
|
|
- Position in request queue
|
|
|
|
**Visual Indicators**:
|
|
- "Request Pending" status shown
|
|
- Queue position displayed (if multiple requests)
|
|
- "Cancel Request" button available
|
|
|
|
**Automatic Behaviors**:
|
|
- Enters this mode after clicking "Request Control"
|
|
- Primary session is notified of the request
|
|
- Returns to observer if request is denied
|
|
- Becomes primary if request is approved
|
|
|
|
### Pending Mode
|
|
|
|
![Pending Session Screenshot - placeholder for image]
|
|
|
|
**Permissions**: No access until approved
|
|
|
|
Pending sessions have:
|
|
- **No video access** - Screen shows "Waiting for approval"
|
|
- **No system access** - Cannot view or interact with anything
|
|
- Optional nickname field (if required by settings)
|
|
|
|
**Visual Indicators**:
|
|
- "Waiting for approval" message
|
|
- Nickname input field (if required)
|
|
- No video feed or controls visible
|
|
|
|
**Automatic Behaviors**:
|
|
- Only active when "Require Approval" setting is enabled
|
|
- Session must be approved by current primary
|
|
- Becomes observer after approval
|
|
- Automatically removed after 1 minute if not approved (security measure)
|
|
|
|
---
|
|
|
|
## Session Settings
|
|
|
|
Session behavior is controlled through global settings that affect all current and future sessions.
|
|
|
|
### Require Approval
|
|
|
|
**Default**: `false`
|
|
**Type**: Boolean
|
|
|
|
When enabled, new sessions must be explicitly approved by the current primary session before gaining access.
|
|
|
|
**Use Cases**:
|
|
- Secure environments where unauthorized viewing should be prevented
|
|
- Limiting access to trusted users only
|
|
- Preventing session spam or unauthorized monitoring
|
|
|
|
**Behavior**:
|
|
- New sessions start in Pending mode with no video access
|
|
- Primary receives notification with session details (source, identity, nickname)
|
|
- Primary can approve or deny access
|
|
- If no primary exists, the system uses emergency promotion to select a trusted session
|
|
|
|
**Security Note**: This setting prevents unauthorized video access but requires a primary session to approve new connections. If the primary disconnects, the system will automatically promote the most trusted pending session to prevent deadlock.
|
|
|
|
### Require Nickname
|
|
|
|
**Default**: `false`
|
|
**Type**: Boolean
|
|
|
|
When enabled, sessions must provide a valid nickname before being approved.
|
|
|
|
**Use Cases**:
|
|
- Identify multiple concurrent users
|
|
- Maintain audit trail of session activity
|
|
- Prevent anonymous connections
|
|
|
|
**Behavior**:
|
|
- Sessions without nicknames cannot be approved
|
|
- Approval request is delayed until nickname is provided
|
|
- Nickname must be 2-30 characters, alphanumeric with dashes/underscores
|
|
- If disabled, nicknames are auto-generated based on browser type
|
|
|
|
**Auto-Generated Nicknames**:
|
|
When nickname requirement is disabled, sessions receive automatic nicknames in the format:
|
|
- `u-chrome-a1b2` - Chrome browser
|
|
- `u-firefox-c3d4` - Firefox browser
|
|
- `u-safari-e5f6` - Safari browser
|
|
- `u-edge-g7h8` - Edge browser
|
|
- `u-user-i9j0` - Unknown browser
|
|
|
|
### Reconnect Grace Period
|
|
|
|
**Default**: `10` seconds
|
|
**Type**: Integer (1-300 seconds)
|
|
**Configurable**: Yes
|
|
|
|
Grace period duration for primary session reconnection after disconnect.
|
|
|
|
**Purpose**:
|
|
Prevents accidental loss of control due to:
|
|
- Network hiccups
|
|
- Browser tab refresh
|
|
- Page navigation
|
|
- Temporary connection issues
|
|
|
|
**Behavior**:
|
|
- Primary session disconnects → grace period starts
|
|
- Primary slot is reserved for the disconnected session
|
|
- Other sessions cannot become primary during grace period
|
|
- Original session can reclaim primary status if reconnecting within grace period
|
|
- If grace period expires without reconnection, next eligible session is promoted
|
|
|
|
**Technical Details**:
|
|
- Grace period is cleared on intentional logout
|
|
- Manual transfers bypass grace period (immediate promotion)
|
|
- Grace period does not apply to observer sessions (they reconnect as observers)
|
|
|
|
### Primary Timeout
|
|
|
|
**Default**: `300` seconds (5 minutes)
|
|
**Type**: Integer
|
|
**Configurable**: Yes
|
|
**Special Values**: `0` = disabled (no timeout)
|
|
|
|
Inactivity timeout for primary session.
|
|
|
|
**Purpose**:
|
|
- Prevent abandoned sessions from holding control indefinitely
|
|
- Free up primary slot when user walks away
|
|
- Ensure active users have access to control
|
|
|
|
**Activity Tracking**:
|
|
Primary session is considered "active" when:
|
|
- Mouse movement detected
|
|
- Keyboard input sent
|
|
- Any RPC method called
|
|
- WebRTC keep-alive ping received
|
|
|
|
**Behavior**:
|
|
- Timer resets on each activity
|
|
- After timeout expires, primary is demoted to observer
|
|
- Next eligible session is automatically promoted to primary
|
|
- Original session can request control again after demotion
|
|
|
|
### Private Keystrokes
|
|
|
|
**Default**: `false`
|
|
**Type**: Boolean
|
|
|
|
When enabled, keystroke events are only broadcast to the primary session.
|
|
|
|
**Purpose**:
|
|
- Prevent observer sessions from seeing typed passwords
|
|
- Protect sensitive data entry
|
|
- Maintain privacy during credential input
|
|
|
|
**Behavior**:
|
|
- When `false`: All sessions see keystroke notifications (for awareness)
|
|
- When `true`: Only primary session receives keystroke events
|
|
- Mouse movements are always visible to all sessions
|
|
- Does not affect actual input execution (only event visibility)
|
|
|
|
**Use Cases**:
|
|
- Password entry during login
|
|
- Entering API keys or tokens
|
|
- Private configuration data
|
|
- Any sensitive text input
|
|
|
|
### Maximum Rejection Attempts
|
|
|
|
**Default**: `3`
|
|
**Type**: Integer (1-10)
|
|
|
|
Maximum number of times a session can be rejected before automatic blocking.
|
|
|
|
**Purpose**:
|
|
- Prevent spam from repeatedly requesting approval
|
|
- Rate limit approval request DoS attacks
|
|
- Automatic blacklisting of problematic sessions
|
|
|
|
**Behavior**:
|
|
- Counter increments on each denial
|
|
- After reaching limit, session is automatically disconnected
|
|
- Counter resets after 60 seconds of no requests
|
|
- Does not affect legitimate new connection attempts
|
|
|
|
---
|
|
|
|
## Session Lifecycle
|
|
|
|
### Connection Establishment
|
|
|
|
```
|
|
[Browser] → [WebRTC Handshake] → [Session Creation] → [Mode Assignment]
|
|
```
|
|
|
|
1. **Initial Connection**
|
|
- Client connects via WebRTC
|
|
- Session ID generated (UUID)
|
|
- Source and identity extracted from connection metadata
|
|
- Browser type detected from User-Agent
|
|
|
|
2. **Mode Assignment Logic**
|
|
```
|
|
IF session exists in grace period:
|
|
Reconnect to existing session (preserve mode)
|
|
ELSE IF no primary exists AND not blacklisted:
|
|
Assign Primary mode
|
|
ELSE IF approval required AND primary exists:
|
|
Assign Pending mode
|
|
ELSE:
|
|
Assign Observer mode
|
|
```
|
|
|
|
3. **Validation & Broadcasting**
|
|
- Session added to manager
|
|
- Single primary verified (auto-fix if multiple)
|
|
- All sessions notified of new connection
|
|
- Primary notified if approval required
|
|
|
|
### Normal Disconnection
|
|
|
|
```
|
|
[User Logout] → [Clear Grace Period] → [Remove Session] → [Promote Next]
|
|
```
|
|
|
|
**Intentional Logout Flow**:
|
|
1. Session calls logout/disconnect method
|
|
2. Grace period is cleared (marked as intentional)
|
|
3. Session removed from manager
|
|
4. If was primary: immediate promotion of next eligible session
|
|
5. All sessions notified of removal
|
|
|
|
**Characteristics**:
|
|
- No grace period applied
|
|
- Immediate promotion if primary leaves
|
|
- Clean session cleanup
|
|
- No reconnection possibility
|
|
|
|
### Accidental Disconnection
|
|
|
|
```
|
|
[Connection Lost] → [Grace Period Active] → [Await Reconnect | Timeout]
|
|
↓ ↓
|
|
[Reconnect Success] [Promote Next]
|
|
```
|
|
|
|
**Accidental Disconnect Flow**:
|
|
1. Connection drops (network issue, browser issue, etc.)
|
|
2. Grace period started (default 10 seconds)
|
|
3. Session info preserved in reconnection map
|
|
4. Primary slot reserved (if was primary)
|
|
5. **Wait for reconnection**:
|
|
- If reconnects within grace: restore original mode
|
|
- If grace expires: session removed, next promoted
|
|
|
|
**Grace Period Details**:
|
|
- Grace period duration configurable (1-300 seconds)
|
|
- Maximum 10 concurrent grace periods (DoS protection)
|
|
- Oldest entries evicted if limit reached
|
|
- Grace periods cleared on new primary establishment
|
|
|
|
### Reconnection Scenarios
|
|
|
|
#### Scenario 1: Primary Reconnects Within Grace Period
|
|
|
|
```
|
|
Time 0s: Primary disconnects (network issue)
|
|
Time 0s: Grace period starts (10 seconds)
|
|
Time 3s: Primary reconnects → RESTORED as primary ✓
|
|
```
|
|
|
|
**Result**: Session seamlessly reclaims primary status, no interruption to workflow.
|
|
|
|
#### Scenario 2: Primary Reconnects After Grace Expiry
|
|
|
|
```
|
|
Time 0s: Primary disconnects
|
|
Time 0s: Grace period starts (10 seconds)
|
|
Time 12s: Grace expires → Observer B promoted to primary
|
|
Time 15s: Original primary reconnects → Becomes observer ✗
|
|
```
|
|
|
|
**Result**: Original primary returns as observer, must request control if needed.
|
|
|
|
#### Scenario 3: Observer Reconnects
|
|
|
|
```
|
|
Time 0s: Observer disconnects
|
|
Time 0s: Grace period starts
|
|
Time 5s: Observer reconnects → RESTORED as observer ✓
|
|
```
|
|
|
|
**Result**: Observer sessions always reconnect as observers (no primary slot reservation).
|
|
|
|
#### Scenario 4: Multiple Rapid Disconnects
|
|
|
|
```
|
|
Time 0s: Primary A disconnects → Grace period active
|
|
Time 1s: Observer B promoted (emergency)
|
|
Time 2s: Primary B disconnects → Grace period active
|
|
Time 3s: Observer C promoted (emergency, rate limit bypassed)
|
|
```
|
|
|
|
**Result**: System ensures at least one primary always exists, bypassing rate limits when necessary.
|
|
|
|
---
|
|
|
|
## Grace Period & Reconnection
|
|
|
|
### Grace Period Mechanics
|
|
|
|
The grace period is a time window during which a disconnected session can reclaim its previous role without interruption.
|
|
|
|
**Key Properties**:
|
|
- **Per-Session**: Each session gets its own grace period
|
|
- **Mode-Aware**: Different behavior for primary vs observer sessions
|
|
- **Expiration-Based**: Time-bound protection window
|
|
- **Blacklist-Aware**: Respects manual transfer blacklisting
|
|
|
|
### Primary Grace Period
|
|
|
|
When a primary session disconnects accidentally:
|
|
|
|
1. **Grace Period Activation**
|
|
```go
|
|
sm.primarySessionID = "" // Clear active slot
|
|
sm.lastPrimaryID = sessionID // Remember who it was
|
|
sm.reconnectGrace[sessionID] = now + 10s // Set expiration
|
|
```
|
|
|
|
2. **Protection During Grace**
|
|
- Primary slot remains empty (no one can claim it)
|
|
- `lastPrimaryID` tracks the rightful owner
|
|
- Other sessions cannot be promoted to primary
|
|
- Requests are queued instead of immediately granted
|
|
|
|
3. **Successful Reconnection**
|
|
```go
|
|
IF session.ID == sm.lastPrimaryID AND not blacklisted:
|
|
session.Mode = Primary
|
|
sm.primarySessionID = session.ID
|
|
sm.lastPrimaryID = "" // Clear tracking
|
|
```
|
|
|
|
4. **Grace Expiration**
|
|
- After 10 seconds (configurable), grace period expires
|
|
- System promotes most eligible observer to primary
|
|
- If approval required, uses trust-based emergency promotion
|
|
- Original session becomes observer if it reconnects later
|
|
|
|
### Observer Grace Period
|
|
|
|
When an observer session disconnects:
|
|
|
|
1. **Grace Period Activation**
|
|
- Same as primary, but no primary slot reservation
|
|
- Session info stored in reconnection map
|
|
- No impact on other sessions
|
|
|
|
2. **Successful Reconnection**
|
|
- Restores as observer
|
|
- No promotion or special treatment
|
|
- Session resumes viewing
|
|
|
|
3. **Grace Expiration**
|
|
- Session removed from grace map
|
|
- No promotion occurs
|
|
- Session gone forever unless new connection made
|
|
|
|
### Blacklist Interaction
|
|
|
|
**Transfer Blacklisting** (60-second duration):
|
|
When a manual transfer occurs (A → B):
|
|
1. Session A is demoted to observer
|
|
2. Session A is blacklisted for 60 seconds
|
|
3. All other sessions (except B) are blacklisted for 60 seconds
|
|
4. Grace periods for blacklisted sessions are cleared
|
|
|
|
**Purpose**: Prevent immediate re-takeover after manual transfer
|
|
|
|
**Grace Period Impact**:
|
|
- Blacklisted sessions cannot reclaim primary during grace period
|
|
- Reconnection still works, but session becomes observer instead
|
|
- Blacklist expires after 60 seconds, then normal grace period rules apply
|
|
|
|
### Grace Period Edge Cases
|
|
|
|
#### Case 1: Grace Period During Manual Transfer
|
|
|
|
```
|
|
Time 0s: Primary A disconnects → Grace active
|
|
Time 3s: Observer B manually requests control
|
|
Time 3s: Primary A's grace cleared, B promoted
|
|
Time 5s: Primary A reconnects → Becomes observer (blacklisted)
|
|
```
|
|
|
|
**Reason**: Manual user action takes precedence over grace period.
|
|
|
|
#### Case 2: Multiple Grace Periods
|
|
|
|
```
|
|
Primary A disconnects → Grace A active (expires at T+10s)
|
|
Observer B disconnects → Grace B active (expires at T+10s)
|
|
Observer C disconnects → Grace C active (expires at T+10s)
|
|
```
|
|
|
|
**Limit**: Maximum 10 concurrent grace periods (DoS protection)
|
|
**Eviction**: Oldest grace period removed if limit exceeded
|
|
|
|
#### Case 3: Grace Period with Approval Required
|
|
|
|
```
|
|
Primary disconnects → Grace active
|
|
New session connects → Becomes pending (no primary to approve)
|
|
Grace expires → Pending session promoted via emergency promotion
|
|
```
|
|
|
|
**Reason**: System must always have a primary when sessions exist.
|
|
|
|
---
|
|
|
|
## Session Approval System
|
|
|
|
The approval system gates new connections, requiring explicit permission from the current primary before granting access.
|
|
|
|
### Approval Workflow
|
|
|
|
![Approval Workflow Diagram - placeholder for image]
|
|
|
|
```
|
|
[New Session] → [Pending Mode] → [Primary Notified] → [Approve/Deny] → [Observer/Disconnect]
|
|
```
|
|
|
|
#### Step 1: New Session Arrives
|
|
|
|
```go
|
|
IF RequireApproval enabled AND primary exists:
|
|
session.Mode = Pending
|
|
session.hasPermission(VideoView) = false // No video access
|
|
```
|
|
|
|
**Result**: Session sees "Waiting for approval" screen, no video feed.
|
|
|
|
#### Step 2: Primary Notification
|
|
|
|
Primary session receives JSON-RPC event:
|
|
```json
|
|
{
|
|
"method": "newSessionPending",
|
|
"params": {
|
|
"sessionId": "uuid-here",
|
|
"source": "cloud" | "local",
|
|
"identity": "user@example.com",
|
|
"nickname": "user-chrome-a1b2"
|
|
}
|
|
}
|
|
```
|
|
|
|
**UI Display**:
|
|
- Notification badge appears
|
|
- Session list shows pending session with "Approve" and "Deny" buttons
|
|
- Session details visible (source, nickname)
|
|
|
|
#### Step 3: Nickname Validation (if required)
|
|
|
|
```go
|
|
IF RequireNickname enabled AND nickname empty:
|
|
// Wait for nickname before showing approval request
|
|
return
|
|
```
|
|
|
|
**Behavior**:
|
|
- Approval request delayed until nickname provided
|
|
- Pending session shows nickname input field
|
|
- After nickname entered, approval request sent to primary
|
|
|
|
#### Step 4: Primary Decision
|
|
|
|
**Approve Action**:
|
|
```json
|
|
{
|
|
"method": "approveNewSession",
|
|
"params": {
|
|
"sessionId": "uuid-here"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Result**:
|
|
- Session promoted to Observer mode
|
|
- Video access granted
|
|
- Session can now view and request control
|
|
- All sessions notified of mode change
|
|
|
|
**Deny Action**:
|
|
```json
|
|
{
|
|
"method": "denyNewSession",
|
|
"params": {
|
|
"sessionId": "uuid-here"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Result**:
|
|
- Rejection counter incremented
|
|
- Session receives "Access Denied" message
|
|
- Connection closed after 5 seconds
|
|
- If rejection counter exceeds max (default 3), session is blocked
|
|
|
|
### Approval Security
|
|
|
|
#### DoS Protection
|
|
|
|
**Rate Limiting**:
|
|
- Maximum 3 rejections per session (configurable)
|
|
- After reaching limit, session auto-disconnected
|
|
- 60-second cooldown before counter resets
|
|
|
|
**Pending Session Timeout**:
|
|
- Pending sessions removed after 1 minute if not approved
|
|
- Prevents accumulation of stale pending sessions
|
|
- Logged as "timed-out pending session"
|
|
|
|
**Maximum Pending Sessions**:
|
|
- System tracks and limits pending session count
|
|
- Oldest pending sessions removed if limit reached
|
|
|
|
#### Emergency Approval Bypass
|
|
|
|
**Scenario**: Primary disconnects while sessions are pending
|
|
|
|
```
|
|
Primary disconnects → Grace expires → No primary exists
|
|
BUT pending sessions exist → DEADLOCK RISK
|
|
```
|
|
|
|
**Solution**: Emergency promotion with approval bypass
|
|
|
|
```go
|
|
IF no primary exists AND approval required:
|
|
// Find most trusted pending/observer session
|
|
session = findMostTrustedSessionForEmergency()
|
|
session.Mode = Primary // Bypass approval requirement
|
|
LogWarning("EMERGENCY: Bypassing approval to prevent deadlock")
|
|
```
|
|
|
|
**Trust Scoring**:
|
|
Sessions are scored based on:
|
|
- Session age (longer = more trusted, up to 100 points)
|
|
- Previous primary status (+50 points)
|
|
- Current mode (observer +20, queued +10, pending +0)
|
|
- Nickname presence (if required: +15 if present, -30 if missing)
|
|
|
|
**Highest Score Wins**: Most trusted session promoted to prevent deadlock.
|
|
|
|
### Approval Testing Scenarios
|
|
|
|
#### Test 1: Basic Approval Flow
|
|
|
|
1. Enable "Require Approval"
|
|
2. Connect Session A → Becomes primary
|
|
3. Connect Session B → Becomes pending
|
|
4. Session B shows "Waiting for approval", no video
|
|
5. Session A receives notification
|
|
6. Session A approves → Session B becomes observer with video access
|
|
|
|
**Expected**: Clean approval flow, video access granted after approval.
|
|
|
|
#### Test 2: Approval with Nickname Required
|
|
|
|
1. Enable "Require Approval" and "Require Nickname"
|
|
2. Connect Session A → Becomes primary
|
|
3. Connect Session B (no nickname) → Becomes pending
|
|
4. Session B shows nickname input field
|
|
5. Session A does NOT receive notification (waiting for nickname)
|
|
6. Session B enters nickname "TestUser"
|
|
7. Session A NOW receives notification
|
|
8. Session A approves → Session B becomes observer
|
|
|
|
**Expected**: Approval delayed until nickname provided.
|
|
|
|
#### Test 3: Approval Denial
|
|
|
|
1. Enable "Require Approval"
|
|
2. Connect Session A → Becomes primary
|
|
3. Connect Session B → Becomes pending
|
|
4. Session A denies → Session B disconnected
|
|
5. Session B reconnects → Becomes pending again
|
|
6. Session A denies → Rejection counter = 2
|
|
7. Session B reconnects → Becomes pending again
|
|
8. Session A denies → Session B blocked (max rejections reached)
|
|
|
|
**Expected**: After 3 rejections, session auto-blocked.
|
|
|
|
#### Test 4: Emergency Approval Bypass
|
|
|
|
1. Enable "Require Approval"
|
|
2. Connect Session A → Becomes primary
|
|
3. Connect Session B → Becomes pending (no video)
|
|
4. Session A disconnects (close browser)
|
|
5. Grace period starts (10 seconds)
|
|
6. Wait for grace expiration
|
|
7. Session B automatically promoted to primary (emergency bypass)
|
|
|
|
**Expected**: Session B gains primary status despite pending mode.
|
|
|
|
---
|
|
|
|
## Primary Role Transfer
|
|
|
|
The system supports multiple ways to transfer primary control between sessions.
|
|
|
|
### Transfer Types
|
|
|
|
#### 1. Direct Transfer (Manual)
|
|
|
|
**Initiated By**: Current primary session
|
|
**Target**: Specific observer or queued session
|
|
**Method**: `transferPrimary(fromID, toID)`
|
|
|
|
**Flow**:
|
|
```
|
|
Primary A → "Transfer to Session B" → Direct transfer
|
|
```
|
|
|
|
**Behavior**:
|
|
1. Session A demoted to observer
|
|
2. Session A blacklisted for 60 seconds
|
|
3. Session B promoted to primary
|
|
4. Session B blacklist entry removed
|
|
5. All other sessions blacklisted for 60 seconds
|
|
6. Grace periods cleared
|
|
7. `lastPrimaryID` cleared (prevents reconnection as primary)
|
|
|
|
**Use Case**: Primary voluntarily gives control to specific session.
|
|
|
|
**UI**: "Transfer Control" button next to observer sessions.
|
|
|
|
#### 2. Approval Transfer
|
|
|
|
**Initiated By**: Queued session requesting control
|
|
**Target**: Requesting session
|
|
**Method**: `approveRequest(requesterID)`
|
|
|
|
**Flow**:
|
|
```
|
|
Observer B → "Request Control" → Queued → Primary A approves → Transfer
|
|
```
|
|
|
|
**Behavior**:
|
|
- Same as direct transfer
|
|
- Requester removed from queue
|
|
- Primary receives notification of request
|
|
- Approval UI shown in session list
|
|
|
|
**Use Case**: Observer asks for control, primary grants it.
|
|
|
|
#### 3. Release Transfer
|
|
|
|
**Initiated By**: Current primary session
|
|
**Target**: Next eligible observer
|
|
**Method**: `releasePrimary()`
|
|
|
|
**Flow**:
|
|
```
|
|
Primary A → "Release Control" → Find next observer → Auto-promote
|
|
```
|
|
|
|
**Behavior**:
|
|
1. Primary A demoted to observer
|
|
2. System finds next eligible session (not blacklisted)
|
|
3. Selected session promoted to primary
|
|
4. Blacklist applied to protect new primary
|
|
5. Queue order respected (queued sessions first)
|
|
|
|
**Use Case**: Primary gives up control without specifying recipient.
|
|
|
|
**UI**: "Release Control" button in session menu.
|
|
|
|
#### 4. Emergency Promotion
|
|
|
|
**Initiated By**: System (automatic)
|
|
**Target**: Most eligible/trusted session
|
|
**Trigger**: Primary timeout, grace expiration, or deadlock
|
|
|
|
**Types of Emergency Promotion**:
|
|
|
|
**a) Emergency Auto-Promotion** (`emergency_auto_promotion`)
|
|
- Occurs when no primary exists and no grace period active
|
|
- Triggered by validation checks
|
|
- Selects any eligible observer (if approval not required)
|
|
- Uses trust scoring if approval required
|
|
|
|
**b) Emergency Timeout Promotion** (`emergency_timeout_promotion`)
|
|
- Occurs when primary times out due to inactivity
|
|
- Primary timeout default: 5 minutes
|
|
- Trust scoring used if approval required
|
|
- Excludes timed-out session from promotion
|
|
|
|
**c) Emergency Deadlock Prevention** (`emergency_promotion_deadlock_prevention`)
|
|
- Occurs when grace period expires without reconnection
|
|
- Prevents system from having no primary
|
|
- Trust scoring used if approval required
|
|
- Rate limited (30 seconds between emergency promotions)
|
|
|
|
**Rate Limiting**:
|
|
```go
|
|
IF no primary exists:
|
|
Bypass all rate limits // CRITICAL: Must always have primary
|
|
ELSE:
|
|
IF last emergency < 30 seconds ago:
|
|
Block promotion (potential DoS attack)
|
|
IF consecutive emergencies >= 3:
|
|
Block promotion (security protection)
|
|
```
|
|
|
|
**Trust Scoring Algorithm**:
|
|
```
|
|
score = 0
|
|
score += min(sessionAge.Minutes(), 100) // Up to 100 points for age
|
|
IF lastPrimaryID == sessionID:
|
|
score += 50 // Previous primary bonus
|
|
IF mode == Observer:
|
|
score += 20
|
|
ELSE IF mode == Queued:
|
|
score += 10
|
|
IF nickname required AND nickname present:
|
|
score += 15
|
|
IF nickname required AND nickname missing:
|
|
score -= 30
|
|
```
|
|
|
|
### Transfer Protection (Blacklisting)
|
|
|
|
**Purpose**: Prevent unwanted immediate re-takeover after manual transfer.
|
|
|
|
**Duration**: 60 seconds
|
|
|
|
**Applied To**: All sessions except newly promoted primary
|
|
|
|
**Mechanism**:
|
|
```go
|
|
type TransferBlacklistEntry struct {
|
|
SessionID string
|
|
ExpiresAt time.Time
|
|
}
|
|
```
|
|
|
|
**Only Applied During Manual Transfers**:
|
|
- Direct transfer
|
|
- Approval transfer
|
|
- Release transfer
|
|
|
|
**NOT Applied During Emergency Promotions**:
|
|
- Emergency auto-promotion
|
|
- Emergency timeout promotion
|
|
- Emergency deadlock prevention
|
|
- Initial promotion (first session)
|
|
|
|
**Reasoning**:
|
|
- Manual transfers = user-initiated, need protection from immediate reversal
|
|
- Emergency promotions = system-initiated for availability, must happen immediately
|
|
|
|
**Effects**:
|
|
- Blacklisted sessions cannot become primary
|
|
- Blacklisted sessions cannot be selected for promotion
|
|
- Grace period reconnection respects blacklist
|
|
- Expires after 60 seconds automatically
|
|
|
|
### Transfer Testing Scenarios
|
|
|
|
#### Test 1: Direct Transfer
|
|
|
|
1. Session A is primary, Session B is observer
|
|
2. Session A clicks "Transfer to Session B"
|
|
3. Session A becomes observer
|
|
4. Session B becomes primary
|
|
5. Session A is blacklisted for 60 seconds
|
|
6. Session A tries to request control → Blocked by blacklist
|
|
7. Wait 60 seconds
|
|
8. Session A requests control → Allowed
|
|
|
|
**Expected**: Clean transfer with 60-second protection.
|
|
|
|
#### Test 2: Transfer with Refresh
|
|
|
|
1. Session A is primary, Session B is observer
|
|
2. Session A clicks "Transfer to Session B"
|
|
3. Session B becomes primary
|
|
4. Session B refreshes browser (WebRTC reconnection)
|
|
5. Session B reconnects and REMAINS primary
|
|
|
|
**Expected**: Session B does not lose primary status on refresh.
|
|
|
|
#### Test 3: Primary Logout with Observers
|
|
|
|
1. Session A is primary, Sessions B and C are observers
|
|
2. Session A clicks "Logout"
|
|
3. Session A disconnects (intentional)
|
|
4. Session B OR C promoted immediately (no grace period)
|
|
|
|
**Expected**: Instant promotion, no delay.
|
|
|
|
#### Test 4: Primary Timeout
|
|
|
|
1. Session A is primary, Session B is observer
|
|
2. Session A becomes inactive (no input for 5 minutes)
|
|
3. After 5 minutes, Session A demoted to observer
|
|
4. Session B promoted to primary automatically
|
|
5. Session A can request control again
|
|
|
|
**Expected**: Automatic handoff on timeout.
|
|
|
|
---
|
|
|
|
## Emergency Promotion
|
|
|
|
Emergency promotion is the system's safety mechanism to ensure there is always a primary session when sessions exist.
|
|
|
|
### When Emergency Promotion Occurs
|
|
|
|
#### Trigger 1: No Primary Exists (Validation)
|
|
|
|
**Scenario**: System detects no primary during periodic validation
|
|
|
|
```go
|
|
// Runs every 10 seconds
|
|
func validateSinglePrimary() {
|
|
IF primaryCount == 0 AND totalSessions > 0 AND no active grace period:
|
|
EmergencyPromote()
|
|
}
|
|
```
|
|
|
|
**Common Causes**:
|
|
- Bug in session management
|
|
- Race condition during transfers
|
|
- Manual state corruption
|
|
|
|
**Resolution**: Automatic promotion of next eligible session.
|
|
|
|
#### Trigger 2: Primary Timeout
|
|
|
|
**Scenario**: Primary session inactive for too long (default 5 minutes)
|
|
|
|
```go
|
|
IF now - primarySession.LastActive > 5 minutes:
|
|
DemotePrimary()
|
|
EmergencyPromote()
|
|
```
|
|
|
|
**Activity Resets Timer**:
|
|
- Mouse movement
|
|
- Keyboard input
|
|
- Any RPC method call
|
|
- WebRTC ping/pong
|
|
|
|
**Resolution**: Timed-out session demoted, next session promoted.
|
|
|
|
#### Trigger 3: Grace Period Expiration
|
|
|
|
**Scenario**: Primary disconnects, grace period expires without reconnection
|
|
|
|
```go
|
|
IF grace period expired AND lastPrimaryID set:
|
|
ClearPrimarySlot()
|
|
EmergencyPromote()
|
|
```
|
|
|
|
**Timeline**:
|
|
```
|
|
T+0s: Primary disconnects
|
|
T+0s: Grace period starts (10 seconds)
|
|
T+10s: Grace expires
|
|
T+10s: Emergency promotion triggered
|
|
```
|
|
|
|
**Resolution**: Most eligible session promoted to prevent indefinite wait.
|
|
|
|
#### Trigger 4: Multiple Rapid Disconnects
|
|
|
|
**Scenario**: Primary and newly promoted session both disconnect quickly
|
|
|
|
```
|
|
T+0s: Primary A disconnects
|
|
T+1s: Observer B promoted (emergency)
|
|
T+2s: Primary B disconnects (in background tab, ICE gathering stuck)
|
|
T+2s: Observer C promoted (emergency, rate limit bypassed)
|
|
```
|
|
|
|
**Critical Fix**: Rate limits bypassed when no primary exists
|
|
|
|
```go
|
|
hasPrimary := sm.primarySessionID != ""
|
|
IF !hasPrimary:
|
|
LogError("CRITICAL: No primary exists - bypassing all rate limits")
|
|
// Promote immediately, ignore rate limits
|
|
```
|
|
|
|
**Reasoning**: System availability takes precedence over rate limiting when deadlock is imminent.
|
|
|
|
### Emergency Promotion Selection
|
|
|
|
#### Without Approval Requirement
|
|
|
|
**Algorithm**: Simple first-eligible selection
|
|
|
|
```go
|
|
// Check queue first
|
|
IF queueOrder not empty:
|
|
FOR each queued session:
|
|
IF not blacklisted:
|
|
RETURN session
|
|
|
|
// Then check observers
|
|
FOR each session WHERE mode == Observer:
|
|
IF not blacklisted:
|
|
RETURN session
|
|
|
|
// Last resort: pending sessions
|
|
FOR each session WHERE mode == Pending:
|
|
IF not blacklisted:
|
|
RETURN session
|
|
```
|
|
|
|
**Priority Order**:
|
|
1. Queued sessions (in queue order)
|
|
2. Observer sessions (arbitrary order)
|
|
3. Pending sessions (last resort)
|
|
|
|
#### With Approval Requirement
|
|
|
|
**Algorithm**: Trust-based scoring
|
|
|
|
```go
|
|
bestSessionID = ""
|
|
bestScore = -1
|
|
|
|
// First pass: Observers and Queued
|
|
FOR each session WHERE mode IN [Observer, Queued]:
|
|
IF not blacklisted:
|
|
score = calculateTrustScore(session)
|
|
IF score > bestScore:
|
|
bestScore = score
|
|
bestSessionID = session.ID
|
|
|
|
// Second pass: Pending (only if no observers found)
|
|
IF bestSessionID == "":
|
|
FOR each session WHERE mode == Pending:
|
|
IF not blacklisted:
|
|
score = calculateTrustScore(session)
|
|
IF score > bestScore:
|
|
bestScore = score
|
|
bestSessionID = session.ID
|
|
```
|
|
|
|
**Trust Scoring Factors**:
|
|
|
|
| Factor | Points | Reasoning |
|
|
|--------|--------|-----------|
|
|
| Session age | 0-100 | Older sessions more likely legitimate |
|
|
| Was previous primary | +50 | Proven trusted user |
|
|
| Observer mode | +20 | Already has video access |
|
|
| Queued mode | +10 | Actively waiting |
|
|
| Pending mode | +0 | Not yet trusted |
|
|
| Has nickname (when required) | +15 | Engaged user |
|
|
| Missing nickname (when required) | -30 | Incomplete setup |
|
|
|
|
**Example Scoring**:
|
|
```
|
|
Session A: age=2min, observer, nickname="Admin"
|
|
Score = 2 + 20 + 15 = 37
|
|
|
|
Session B: age=30min, was primary, observer, nickname="Bob"
|
|
Score = 30 + 50 + 20 + 15 = 115 ← SELECTED
|
|
|
|
Session C: age=1min, pending, no nickname
|
|
Score = 1 + 0 - 30 = -29
|
|
```
|
|
|
|
### Emergency Promotion Rate Limiting
|
|
|
|
**Purpose**: Prevent DoS attacks via rapid session churn
|
|
|
|
**Limits**:
|
|
- **Time-based**: 30 seconds between emergency promotions
|
|
- **Consecutive**: Maximum 3 consecutive emergency promotions
|
|
- **Bypass**: Both limits bypassed when NO primary exists
|
|
|
|
**Implementation**:
|
|
```go
|
|
isEmergencyPromotion := false
|
|
hasPrimary := sm.primarySessionID != ""
|
|
|
|
IF approvalRequired:
|
|
isEmergencyPromotion = true
|
|
|
|
IF !hasPrimary:
|
|
LogError("CRITICAL: No primary - bypassing rate limits")
|
|
// Promote immediately
|
|
ELSE:
|
|
IF now - lastEmergencyPromotion < 30 seconds:
|
|
LogWarn("Emergency rate limit exceeded")
|
|
RETURN // Skip this promotion
|
|
|
|
IF consecutiveEmergencyPromotions >= 3:
|
|
LogError("Too many consecutive emergencies")
|
|
RETURN // Skip this promotion
|
|
```
|
|
|
|
**Counter Reset**:
|
|
- Consecutive counter resets on successful manual transfer
|
|
- Time limit is absolute (30 second window)
|
|
|
|
**Logging**:
|
|
All emergency promotions logged with:
|
|
- Reason (timeout, grace expiration, deadlock prevention)
|
|
- Selected session ID
|
|
- Trust score (if approval required)
|
|
- Whether rate limits were bypassed
|
|
|
|
### Emergency Promotion Testing
|
|
|
|
#### Test 1: No Primary Detection
|
|
|
|
1. Start with Session A as primary
|
|
2. Manually corrupt state: `sm.primarySessionID = ""`
|
|
3. Wait 10 seconds (validation runs)
|
|
4. Session A should be re-detected and set as primary
|
|
|
|
**Expected**: System auto-corrects invalid state.
|
|
|
|
#### Test 2: Primary Timeout
|
|
|
|
1. Session A becomes primary
|
|
2. Stop all activity (don't move mouse, don't type)
|
|
3. Wait 5 minutes
|
|
4. Session B should be promoted automatically
|
|
5. Session A should become observer
|
|
|
|
**Expected**: Clean timeout and promotion.
|
|
|
|
#### Test 3: Grace Period Expiration
|
|
|
|
1. Session A is primary, Session B is observer
|
|
2. Session A disconnects (network disconnect simulation)
|
|
3. Grace period starts (10 seconds)
|
|
4. Wait 15 seconds (let grace expire)
|
|
5. Session B promoted to primary
|
|
|
|
**Expected**: Session B takes over after grace expires.
|
|
|
|
#### Test 4: Rapid Disconnect Handling
|
|
|
|
1. Session A is primary, Sessions B and C are observers
|
|
2. Session A disconnects
|
|
3. Session B promoted (emergency)
|
|
4. IMMEDIATELY: Session B disconnects
|
|
5. Session C promoted (rate limit bypassed)
|
|
|
|
**Expected**: System maintains primary availability despite rapid disconnects.
|
|
|
|
#### Test 5: Emergency with Approval
|
|
|
|
1. Enable "Require Approval"
|
|
2. Session A is primary
|
|
3. Sessions B and C are pending (not approved)
|
|
4. Session A disconnects permanently
|
|
5. Grace expires
|
|
6. Session B OR C promoted (trust scoring selects highest)
|
|
|
|
**Expected**: System bypasses approval requirement to prevent deadlock.
|
|
|
|
---
|
|
|
|
## Permissions System
|
|
|
|
The permission system controls what actions each session mode can perform.
|
|
|
|
### Permission Model
|
|
|
|
**Architecture**: Role-Based Access Control (RBAC)
|
|
|
|
Each session mode has a predefined set of permissions that cannot be modified at runtime. This ensures consistent security boundaries.
|
|
|
|
### Permission Categories
|
|
|
|
#### Video & Display
|
|
- `video.view` - View video feed
|
|
|
|
#### Input Control
|
|
- `keyboard.input` - Send keyboard input
|
|
- `mouse.input` - Send mouse input
|
|
- `clipboard.paste` - Paste from clipboard
|
|
|
|
#### Session Management
|
|
- `session.transfer` - Transfer primary to another session
|
|
- `session.approve` - Approve pending sessions
|
|
- `session.kick` - Disconnect other sessions
|
|
- `session.request_primary` - Request primary control
|
|
- `session.release_primary` - Release primary control
|
|
- `session.manage` - Modify session settings
|
|
|
|
#### Power & Hardware
|
|
- `power.control` - ATX/DC power control
|
|
- `usb.control` - USB device management
|
|
|
|
#### Mount & Media
|
|
- `mount.media` - Mount virtual media
|
|
- `mount.unmedia` - Unmount virtual media
|
|
- `mount.list` - View mounted media
|
|
|
|
#### Extensions
|
|
- `extension.manage` - Enable/disable extensions
|
|
- `extension.atx` - ATX power extension
|
|
- `extension.dc` - DC power extension
|
|
- `extension.serial` - Serial console extension
|
|
- `extension.wol` - Wake-on-LAN extension
|
|
|
|
#### Terminal & Serial
|
|
- `terminal.access` - SSH terminal access
|
|
- `serial.access` - Serial console access
|
|
|
|
#### Settings
|
|
- `settings.read` - Read system settings
|
|
- `settings.write` - Modify system settings
|
|
- `settings.access` - Access settings UI
|
|
|
|
#### System Operations
|
|
- `system.reboot` - Reboot JetKVM
|
|
- `system.update` - Update firmware
|
|
- `system.network` - Network configuration
|
|
|
|
### Permissions by Mode
|
|
|
|
#### Primary Permissions
|
|
|
|
Primary sessions have **ALL** permissions:
|
|
|
|
```
|
|
✓ video.view
|
|
✓ keyboard.input
|
|
✓ mouse.input
|
|
✓ clipboard.paste
|
|
✓ session.transfer
|
|
✓ session.approve
|
|
✓ session.kick
|
|
✓ session.release_primary
|
|
✓ session.manage
|
|
✓ power.control
|
|
✓ usb.control
|
|
✓ mount.media
|
|
✓ mount.unmedia
|
|
✓ mount.list
|
|
✓ extension.* (all)
|
|
✓ terminal.access
|
|
✓ serial.access
|
|
✓ settings.* (all)
|
|
✓ system.* (all)
|
|
|
|
✗ session.request_primary (not needed)
|
|
```
|
|
|
|
#### Observer Permissions
|
|
|
|
Observer sessions have **limited** permissions:
|
|
|
|
```
|
|
✓ video.view
|
|
✓ session.request_primary
|
|
✓ mount.list (view only)
|
|
|
|
✗ All other permissions denied
|
|
```
|
|
|
|
#### Queued Permissions
|
|
|
|
Queued sessions have **same as observer**:
|
|
|
|
```
|
|
✓ video.view
|
|
✓ session.request_primary
|
|
|
|
✗ All other permissions denied
|
|
```
|
|
|
|
#### Pending Permissions
|
|
|
|
Pending sessions have **NO** permissions:
|
|
|
|
```
|
|
✗ video.view (no video access)
|
|
✗ ALL other permissions denied
|
|
```
|
|
|
|
### Permission Enforcement
|
|
|
|
**Check Timing**: Permissions are checked at multiple layers:
|
|
|
|
1. **RPC Method Call** - Before executing any JSON-RPC method
|
|
2. **UI Rendering** - Frontend hides unavailable controls
|
|
3. **WebRTC Channels** - Input channels only active for primary
|
|
|
|
**Enforcement Flow**:
|
|
```go
|
|
// RPC handler
|
|
func handleRPCMethod(session *Session, method string) {
|
|
requiredPermission := GetMethodPermission(method)
|
|
|
|
IF session does NOT have requiredPermission:
|
|
RETURN "Permission denied: {permission}"
|
|
|
|
// Execute method
|
|
}
|
|
```
|
|
|
|
**Permission Errors**:
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": -32000,
|
|
"message": "Permission denied: keyboard.input"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Method-to-Permission Mapping
|
|
|
|
**Power Control**:
|
|
```
|
|
setATXPowerAction → power.control
|
|
setDCPowerState → power.control
|
|
setDCRestoreState → power.control
|
|
```
|
|
|
|
**USB Management**:
|
|
```
|
|
setUsbDeviceState → usb.control
|
|
setUsbDevices → usb.control
|
|
```
|
|
|
|
**Mount Operations**:
|
|
```
|
|
mountUsb → mount.media
|
|
unmountUsb → mount.media
|
|
mountBuiltInImage → mount.media
|
|
getMassStorageMode → mount.list (read-only)
|
|
```
|
|
|
|
**Settings Operations**:
|
|
```
|
|
setNetworkSettings → settings.write
|
|
setVideoFramerate → settings.write
|
|
setSessionSettings → session.manage
|
|
getNetworkSettings → settings.read
|
|
```
|
|
|
|
**Session Operations**:
|
|
```
|
|
approveNewSession → session.approve
|
|
denyNewSession → session.approve
|
|
transferSession → session.transfer
|
|
requestPrimary → session.request_primary
|
|
releasePrimary → session.release_primary
|
|
```
|
|
|
|
**Input Operations**:
|
|
```
|
|
keyboardReport → keyboard.input
|
|
keypressReport → keyboard.input
|
|
absMouseReport → mouse.input
|
|
relMouseReport → mouse.input
|
|
```
|
|
|
|
*See full mapping in `/internal/session/permissions.go` lines 148-300*
|
|
|
|
### Permission Testing
|
|
|
|
#### Test 1: Observer Input Block
|
|
|
|
1. Session A is primary, Session B is observer
|
|
2. Session B attempts keyboard input via dev console:
|
|
```javascript
|
|
ws.send(JSON.stringify({
|
|
method: "keyboardReport",
|
|
params: { keys: ["a"] }
|
|
}))
|
|
```
|
|
3. Should receive: `Permission denied: keyboard.input`
|
|
|
|
**Expected**: Observer cannot send input.
|
|
|
|
#### Test 2: Observer Settings Block
|
|
|
|
1. Session B (observer) tries to access Settings page
|
|
2. Should see "Permission Denied" or redirect
|
|
3. Cannot view or modify any settings
|
|
|
|
**Expected**: Settings completely inaccessible to observers.
|
|
|
|
#### Test 3: Primary Full Access
|
|
|
|
1. Session A is primary
|
|
2. Verify can access:
|
|
- All settings pages
|
|
- Power controls
|
|
- Mount/unmount media
|
|
- Session management
|
|
- Input controls
|
|
|
|
**Expected**: Primary has unrestricted access.
|
|
|
|
#### Test 4: Pending No Video
|
|
|
|
1. Enable "Require Approval"
|
|
2. Session B connects → Pending mode
|
|
3. Session B should see NO video feed
|
|
4. Session B cannot call `getVideoState` (permission denied)
|
|
|
|
**Expected**: Pending sessions completely blocked from video.
|
|
|
|
---
|
|
|
|
## Session Identification
|
|
|
|
Sessions are identified through multiple attributes for security, auditing, and user recognition.
|
|
|
|
### Session Identity Components
|
|
|
|
#### Session ID (UUID)
|
|
|
|
**Format**: UUID v4 (e.g., `f47ac10b-58cc-4372-a567-0e02b2c3d479`)
|
|
|
|
**Properties**:
|
|
- **Unique**: Globally unique identifier
|
|
- **Persistent**: Maintained across reconnections (within grace period)
|
|
- **Generated**: Server-side on first connection
|
|
- **Used For**: All internal session tracking and RPC references
|
|
|
|
**Reconnection**:
|
|
```go
|
|
IF session.ID exists in grace period map:
|
|
Reconnect to existing session
|
|
ELSE:
|
|
Create new session with new UUID
|
|
```
|
|
|
|
#### Source
|
|
|
|
**Values**: `"cloud"` or `"local"`
|
|
|
|
**Determination**:
|
|
- `cloud` - Connection via cloud relay (using cloud token)
|
|
- `local` - Direct local network connection
|
|
|
|
**Used For**:
|
|
- Session display in UI
|
|
- Trust scoring (local connections may be trusted more)
|
|
- Audit logging
|
|
|
|
**Visual**:
|
|
- Cloud sessions show cloud icon
|
|
- Local sessions show local network icon
|
|
|
|
#### Identity
|
|
|
|
**Format**: String (email, username, or IP address)
|
|
|
|
**Sources**:
|
|
- Cloud connections: User's email from OAuth
|
|
- Local connections: IP address or configured username
|
|
|
|
**Properties**:
|
|
- **Not Unique**: Multiple sessions can have same identity
|
|
- **Display**: Shown in session list
|
|
- **Validation**: Used to verify reconnection legitimacy
|
|
|
|
**Example**:
|
|
```
|
|
Cloud: "alice@example.com"
|
|
Local: "192.168.1.100"
|
|
```
|
|
|
|
#### Nickname
|
|
|
|
**Format**: 2-30 characters, alphanumeric with dashes/underscores
|
|
|
|
**Pattern**: `^[a-zA-Z0-9_-]+$`
|
|
|
|
**Sources**:
|
|
- **User-provided**: When "Require Nickname" enabled
|
|
- **Auto-generated**: When nickname not required
|
|
|
|
**Auto-Generation Format**:
|
|
```
|
|
u-{browser}-{short-id}
|
|
|
|
Examples:
|
|
u-chrome-a1b2
|
|
u-firefox-c3d4
|
|
u-safari-e5f6
|
|
u-edge-g7h8
|
|
u-user-i9j0 (unknown browser)
|
|
```
|
|
|
|
**Short ID**: Last 4 characters of session UUID (lowercase)
|
|
|
|
**Used For**:
|
|
- User-friendly identification
|
|
- Session list display
|
|
- Approval notifications
|
|
- Audit logs
|
|
|
|
**Validation**:
|
|
```go
|
|
IF len(nickname) < 2:
|
|
ERROR "Nickname must be at least 2 characters"
|
|
IF len(nickname) > 30:
|
|
ERROR "Nickname must be 30 characters or less"
|
|
IF !matches pattern:
|
|
ERROR "Nickname can only contain letters, numbers, dashes, and underscores"
|
|
```
|
|
|
|
#### Browser Type
|
|
|
|
**Detected From**: User-Agent header
|
|
|
|
**Supported Browsers**:
|
|
- `chrome` - Chrome/Chromium
|
|
- `firefox` - Firefox
|
|
- `safari` - Safari
|
|
- `edge` - Edge
|
|
- `opera` - Opera
|
|
- `user` - Unknown/other
|
|
|
|
**Detection Logic**:
|
|
```go
|
|
ua := strings.ToLower(userAgent)
|
|
|
|
IF contains "edg/" OR "edge":
|
|
RETURN "edge"
|
|
ELSE IF contains "firefox":
|
|
RETURN "firefox"
|
|
ELSE IF contains "chrome":
|
|
RETURN "chrome"
|
|
ELSE IF contains "safari" AND NOT "chrome":
|
|
RETURN "safari"
|
|
ELSE IF contains "opera" OR "opr/":
|
|
RETURN "opera"
|
|
ELSE:
|
|
RETURN "user"
|
|
```
|
|
|
|
**Used For**:
|
|
- Auto-generating nicknames
|
|
- Browser-specific UI adjustments
|
|
- Debugging connection issues
|
|
|
|
#### Created At
|
|
|
|
**Type**: Timestamp (RFC 3339)
|
|
|
|
**Set**: On initial session creation
|
|
|
|
**Persistent**: Maintained across reconnections
|
|
|
|
**Used For**:
|
|
- Session age display
|
|
- Trust scoring (older = more trusted)
|
|
- Audit logs
|
|
|
|
#### Last Active
|
|
|
|
**Type**: Timestamp (RFC 3339)
|
|
|
|
**Updated On**:
|
|
- Mouse movement
|
|
- Keyboard input
|
|
- Any RPC method call
|
|
- WebRTC ping/pong
|
|
|
|
**Used For**:
|
|
- Primary timeout calculation
|
|
- Inactivity detection
|
|
- Session sorting (most recent first)
|
|
|
|
### Session Display in UI
|
|
|
|
**Session List Format**:
|
|
|
|
```
|
|
┌─────────────────────────────────────────────┐
|
|
│ 👤 u-chrome-a1b2 [PRIMARY] │
|
|
│ alice@example.com │
|
|
│ Local • Active 2m ago │
|
|
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
|
|
│ │
|
|
│ 👤 u-firefox-c3d4 [OBSERVER] │
|
|
│ bob@example.com │
|
|
│ Cloud • Active 5s ago │
|
|
│ [Request Control] │
|
|
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
|
|
│ │
|
|
│ 👤 TestUser [QUEUED] │
|
|
│ charlie@example.com │
|
|
│ Local • Active 1m ago │
|
|
│ Request Pending (#1 in queue) │
|
|
│ [Approve] [Deny] │
|
|
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
|
|
│ │
|
|
│ 👤 (pending) [PENDING] │
|
|
│ david@example.com │
|
|
│ Cloud • Connected 30s ago │
|
|
│ Waiting for approval │
|
|
│ [Approve] [Deny] │
|
|
└─────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Display Elements**:
|
|
- **Nickname**: Large, prominent
|
|
- **Mode Badge**: Color-coded (Primary=green, Observer=blue, Queued=yellow, Pending=gray)
|
|
- **Identity**: Smaller, secondary text
|
|
- **Source Icon**: Cloud or local network icon
|
|
- **Activity**: Relative time ("2m ago", "just now")
|
|
- **Actions**: Context-specific buttons
|
|
|
|
### Identity Security
|
|
|
|
**Session Hijacking Prevention**:
|
|
|
|
When a session reconnects:
|
|
```go
|
|
IF existing.Identity != session.Identity:
|
|
RETURN "Session ID already in use by different user"
|
|
IF existing.Source != session.Source:
|
|
RETURN "Session ID already in use by different user"
|
|
```
|
|
|
|
**Reasoning**: Prevents malicious actors from stealing session IDs.
|
|
|
|
**Nickname Spoofing Prevention**:
|
|
- Nicknames validated server-side
|
|
- Cannot impersonate other sessions
|
|
- Auto-generated nicknames use session-specific data
|
|
|
|
---
|
|
|
|
## Testing Guide
|
|
|
|
This section provides comprehensive testing scenarios for all multi-session features.
|
|
|
|
### Test Environment Setup
|
|
|
|
**Prerequisites**:
|
|
1. JetKVM device (hardware or development environment)
|
|
2. Multiple browsers (Chrome, Firefox, Safari) for multi-session testing
|
|
3. Network access (local and/or cloud)
|
|
4. Admin access to session settings
|
|
|
|
**Recommended Setup**:
|
|
```
|
|
Browser 1 (Chrome): Primary session
|
|
Browser 2 (Firefox): Observer session
|
|
Browser 3 (Safari): Test session
|
|
```
|
|
|
|
**Configuration Reset**:
|
|
Before each test suite, reset to defaults:
|
|
```json
|
|
{
|
|
"requireApproval": false,
|
|
"requireNickname": false,
|
|
"reconnectGrace": 10,
|
|
"primaryTimeout": 300,
|
|
"privateKeystrokes": false,
|
|
"maxRejectionAttempts": 3
|
|
}
|
|
```
|
|
|
|
### Core Functionality Tests
|
|
|
|
#### TEST-001: Basic Multi-Session
|
|
|
|
**Objective**: Verify multiple sessions can connect simultaneously
|
|
|
|
**Steps**:
|
|
1. Connect with Browser 1 → Verify becomes primary
|
|
2. Connect with Browser 2 → Verify becomes observer
|
|
3. Connect with Browser 3 → Verify becomes observer
|
|
4. Check session list shows all 3 sessions
|
|
5. Verify Browser 1 can control, Browsers 2&3 can only view
|
|
|
|
**Expected**:
|
|
- ✓ 3 sessions visible in session list
|
|
- ✓ 1 primary, 2 observers
|
|
- ✓ Only primary can send input
|
|
- ✓ All sessions see video feed
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-002: Primary Transfer
|
|
|
|
**Objective**: Verify primary role can be transferred between sessions
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browser 2 is observer
|
|
2. Browser 1 clicks "Transfer Control" next to Browser 2
|
|
3. Verify Browser 2 becomes primary
|
|
4. Verify Browser 1 becomes observer
|
|
5. Verify Browser 2 can now send input
|
|
6. Verify Browser 1 cannot send input
|
|
|
|
**Expected**:
|
|
- ✓ Smooth transition of primary role
|
|
- ✓ UI updates immediately
|
|
- ✓ Input control switches correctly
|
|
- ✓ No video interruption
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-003: Request and Approve Control
|
|
|
|
**Objective**: Verify observers can request and receive control
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browser 2 is observer
|
|
2. Browser 2 clicks "Request Control"
|
|
3. Verify Browser 2 enters queued state
|
|
4. Verify Browser 1 sees approval notification
|
|
5. Browser 1 clicks "Approve"
|
|
6. Verify Browser 2 becomes primary
|
|
7. Verify Browser 1 becomes observer
|
|
|
|
**Expected**:
|
|
- ✓ Request notification appears for primary
|
|
- ✓ Queued state displayed correctly
|
|
- ✓ Approval transfers control smoothly
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-004: Request and Deny Control
|
|
|
|
**Objective**: Verify primary can deny control requests
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browser 2 is observer
|
|
2. Browser 2 clicks "Request Control"
|
|
3. Browser 1 clicks "Deny"
|
|
4. Verify Browser 2 returns to observer state
|
|
5. Verify Browser 1 remains primary
|
|
6. Verify Browser 2 can request again
|
|
|
|
**Expected**:
|
|
- ✓ Denial returns session to observer
|
|
- ✓ No disruption to primary
|
|
- ✓ Can request multiple times (until limit)
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-005: Release Control
|
|
|
|
**Objective**: Verify primary can voluntarily release control
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browsers 2&3 are observers
|
|
2. Browser 1 clicks "Release Control"
|
|
3. Verify Browser 1 becomes observer
|
|
4. Verify Browser 2 OR Browser 3 becomes primary (system selects)
|
|
5. Verify new primary can send input
|
|
|
|
**Expected**:
|
|
- ✓ Primary releases control successfully
|
|
- ✓ Observer auto-promoted
|
|
- ✓ All sessions functioning correctly
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
### Grace Period Tests
|
|
|
|
#### TEST-006: Grace Period Reconnection (Success)
|
|
|
|
**Objective**: Verify primary can reconnect within grace period
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary
|
|
2. Close Browser 1 tab (simulating accidental close)
|
|
3. Wait 3 seconds (within 10-second grace)
|
|
4. Reopen Browser 1 (same session ID via browser history)
|
|
5. Verify reconnects as primary
|
|
|
|
**Expected**:
|
|
- ✓ Session reconnects successfully
|
|
- ✓ Primary status restored
|
|
- ✓ No interruption to other sessions
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-007: Grace Period Expiration
|
|
|
|
**Objective**: Verify grace period expires and promotes observer
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browser 2 is observer
|
|
2. Close Browser 1 tab
|
|
3. Wait 15 seconds (exceeds 10-second grace)
|
|
4. Verify Browser 2 becomes primary
|
|
5. Reopen Browser 1
|
|
6. Verify Browser 1 reconnects as observer
|
|
|
|
**Expected**:
|
|
- ✓ Browser 2 promoted after grace expires
|
|
- ✓ Browser 1 returns as observer
|
|
- ✓ No double-primary state
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-008: Intentional Logout (No Grace Period)
|
|
|
|
**Objective**: Verify logout bypasses grace period
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browser 2 is observer
|
|
2. Browser 1 clicks "Logout" button
|
|
3. Verify Browser 2 immediately becomes primary (no 10-second wait)
|
|
4. Reopen Browser 1
|
|
5. Verify Browser 1 enters as new session (observer)
|
|
|
|
**Expected**:
|
|
- ✓ Immediate promotion on logout
|
|
- ✓ No grace period applied
|
|
- ✓ Clean session cleanup
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
### Approval System Tests
|
|
|
|
#### TEST-009: Basic Approval Flow
|
|
|
|
**Objective**: Verify approval system gates new connections
|
|
|
|
**Steps**:
|
|
1. Enable "Require Approval"
|
|
2. Browser 1 connects → Becomes primary
|
|
3. Browser 2 connects → Becomes pending
|
|
4. Verify Browser 2 sees "Waiting for approval" (no video)
|
|
5. Verify Browser 1 sees approval notification
|
|
6. Browser 1 clicks "Approve"
|
|
7. Verify Browser 2 becomes observer with video access
|
|
|
|
**Expected**:
|
|
- ✓ Pending session has no video access
|
|
- ✓ Approval notification displayed
|
|
- ✓ Video access granted after approval
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-010: Approval with Nickname Required
|
|
|
|
**Objective**: Verify nickname requirement gates approval
|
|
|
|
**Steps**:
|
|
1. Enable "Require Approval" and "Require Nickname"
|
|
2. Browser 1 connects → Becomes primary
|
|
3. Browser 2 connects (no nickname provided) → Becomes pending
|
|
4. Verify Browser 1 does NOT see approval notification yet
|
|
5. Browser 2 enters nickname "TestUser"
|
|
6. Verify Browser 1 NOW sees approval notification
|
|
7. Browser 1 approves
|
|
8. Verify Browser 2 becomes observer
|
|
|
|
**Expected**:
|
|
- ✓ Approval delayed until nickname provided
|
|
- ✓ Nickname requirement enforced
|
|
- ✓ Approval proceeds after nickname entered
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-011: Approval Denial and Rejection Limit
|
|
|
|
**Objective**: Verify rejection limit blocks spam
|
|
|
|
**Steps**:
|
|
1. Enable "Require Approval", set max rejections = 3
|
|
2. Browser 1 is primary
|
|
3. Browser 2 connects → Pending
|
|
4. Browser 1 denies → Browser 2 disconnected (count=1)
|
|
5. Browser 2 reconnects → Pending
|
|
6. Browser 1 denies → Browser 2 disconnected (count=2)
|
|
7. Browser 2 reconnects → Pending
|
|
8. Browser 1 denies → Browser 2 blocked (count=3)
|
|
9. Browser 2 tries to reconnect → Connection rejected
|
|
|
|
**Expected**:
|
|
- ✓ Rejection counter increments
|
|
- ✓ After 3 rejections, session auto-blocked
|
|
- ✓ Cannot reconnect after blocking
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-012: Emergency Approval Bypass
|
|
|
|
**Objective**: Verify pending session promoted if primary leaves
|
|
|
|
**Steps**:
|
|
1. Enable "Require Approval"
|
|
2. Browser 1 is primary
|
|
3. Browser 2 connects → Pending (no video)
|
|
4. Browser 1 disconnects permanently (close browser)
|
|
5. Wait 15 seconds (grace period expires)
|
|
6. Verify Browser 2 promoted to primary (approval bypassed)
|
|
7. Verify Browser 2 now has video access and full control
|
|
|
|
**Expected**:
|
|
- ✓ Pending session promoted to prevent deadlock
|
|
- ✓ Emergency bypass logged
|
|
- ✓ System maintains availability
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
### Timeout Tests
|
|
|
|
#### TEST-013: Primary Inactivity Timeout
|
|
|
|
**Objective**: Verify inactive primary is demoted
|
|
|
|
**Steps**:
|
|
1. Set primary timeout = 60 seconds (for faster testing)
|
|
2. Browser 1 is primary, Browser 2 is observer
|
|
3. Browser 1: Do NOT move mouse or type for 60 seconds
|
|
4. After 60 seconds, verify Browser 1 demoted to observer
|
|
5. Verify Browser 2 promoted to primary
|
|
|
|
**Expected**:
|
|
- ✓ Timeout triggers at configured interval
|
|
- ✓ Inactive session demoted
|
|
- ✓ Observer auto-promoted
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-014: Timeout with Activity
|
|
|
|
**Objective**: Verify activity resets timeout timer
|
|
|
|
**Steps**:
|
|
1. Set primary timeout = 60 seconds
|
|
2. Browser 1 is primary
|
|
3. Wait 50 seconds
|
|
4. Browser 1: Move mouse (reset timer)
|
|
5. Wait another 50 seconds
|
|
6. Browser 1: Type something (reset timer)
|
|
7. Wait 50 seconds
|
|
8. Verify Browser 1 still primary (timeout keeps resetting)
|
|
|
|
**Expected**:
|
|
- ✓ Activity prevents timeout
|
|
- ✓ Timer resets on each action
|
|
- ✓ Primary retained indefinitely with activity
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
### Permission Tests
|
|
|
|
#### TEST-015: Observer Input Block
|
|
|
|
**Objective**: Verify observers cannot send input
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browser 2 is observer
|
|
2. Browser 2: Try clicking on video feed
|
|
3. Browser 2: Try typing
|
|
4. Browser 2: Open browser console, try:
|
|
```javascript
|
|
// Send keyboard input via RPC
|
|
rpc.call("keyboardReport", {keys: ["a"]})
|
|
```
|
|
5. Verify all input attempts blocked
|
|
6. Verify error: "Permission denied: keyboard.input"
|
|
|
|
**Expected**:
|
|
- ✓ UI input ignored for observers
|
|
- ✓ RPC calls return permission error
|
|
- ✓ No input reaches device
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-016: Observer Settings Block
|
|
|
|
**Objective**: Verify observers cannot access settings
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browser 2 is observer
|
|
2. Browser 2: Try to navigate to Settings page
|
|
3. Verify redirected or blocked
|
|
4. Browser 2: Open console, try:
|
|
```javascript
|
|
rpc.call("setNetworkSettings", {dhcp: false})
|
|
```
|
|
5. Verify error: "Permission denied: settings.write"
|
|
|
|
**Expected**:
|
|
- ✓ Settings page inaccessible
|
|
- ✓ Settings RPC calls blocked
|
|
- ✓ No configuration changes possible
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-017: Pending Video Block
|
|
|
|
**Objective**: Verify pending sessions have no video access
|
|
|
|
**Steps**:
|
|
1. Enable "Require Approval"
|
|
2. Browser 1 is primary
|
|
3. Browser 2 connects → Pending
|
|
4. Verify Browser 2 shows "Waiting for approval" message
|
|
5. Verify Browser 2 has NO video feed
|
|
6. Browser 2: Open console, try:
|
|
```javascript
|
|
rpc.call("getVideoState", {})
|
|
```
|
|
7. Verify error: "Permission denied: video.view"
|
|
|
|
**Expected**:
|
|
- ✓ No video feed visible
|
|
- ✓ Video RPC calls blocked
|
|
- ✓ Complete video blackout until approved
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
### Edge Case Tests
|
|
|
|
#### TEST-018: Multiple Rapid Disconnects
|
|
|
|
**Objective**: Verify system maintains primary during rapid churn
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browsers 2&3 are observers
|
|
2. Close Browser 1 → Browser 2 promoted
|
|
3. IMMEDIATELY close Browser 2 (within 1 second) → Browser 3 promoted
|
|
4. Verify Browser 3 is now primary
|
|
5. Verify rate limiting did NOT block promotion
|
|
|
|
**Expected**:
|
|
- ✓ System always maintains a primary
|
|
- ✓ Rate limits bypassed when necessary
|
|
- ✓ No deadlock state
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-019: Transfer with Immediate Refresh
|
|
|
|
**Objective**: Verify transferred session retains primary on refresh
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browser 2 is observer
|
|
2. Browser 1 transfers to Browser 2
|
|
3. Browser 2 becomes primary
|
|
4. IMMEDIATELY: Browser 2 refresh page (F5)
|
|
5. Verify Browser 2 reconnects as primary
|
|
6. Verify Browser 1 remains observer
|
|
|
|
**Expected**:
|
|
- ✓ Refresh does not lose primary status
|
|
- ✓ No reversion to previous primary
|
|
- ✓ Transfer is permanent
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-020: Blacklist Expiration
|
|
|
|
**Objective**: Verify transfer blacklist expires after 60 seconds
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary, Browser 2 is observer
|
|
2. Browser 1 transfers to Browser 2
|
|
3. Browser 1 now observer and blacklisted
|
|
4. Browser 1 tries to request control → Blocked (blacklisted)
|
|
5. Wait 60 seconds
|
|
6. Browser 1 tries to request control → Allowed
|
|
7. Browser 2 approves
|
|
8. Verify Browser 1 becomes primary
|
|
|
|
**Expected**:
|
|
- ✓ Blacklist blocks immediate re-takeover
|
|
- ✓ Blacklist expires after 60 seconds
|
|
- ✓ Normal flow resumes after expiration
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
### Stress Tests
|
|
|
|
#### TEST-021: Maximum Session Limit
|
|
|
|
**Objective**: Verify maximum session limit enforced
|
|
|
|
**Steps**:
|
|
1. Connect 10 browsers (maximum)
|
|
2. Verify all 10 sessions accepted
|
|
3. Attempt to connect 11th browser
|
|
4. Verify connection rejected with "Maximum sessions reached"
|
|
|
|
**Expected**:
|
|
- ✓ Exactly 10 sessions allowed
|
|
- ✓ 11th connection rejected
|
|
- ✓ Existing sessions unaffected
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-022: Rapid Connect/Disconnect
|
|
|
|
**Objective**: Verify system stable under rapid session churn
|
|
|
|
**Steps**:
|
|
1. Script or manually: Connect and disconnect 20 sessions rapidly
|
|
2. Monitor system logs for errors
|
|
3. Verify session manager remains stable
|
|
4. Verify at least one session becomes primary
|
|
|
|
**Expected**:
|
|
- ✓ No crashes or deadlocks
|
|
- ✓ System maintains consistency
|
|
- ✓ Clean session cleanup
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-023: Grace Period DoS Protection
|
|
|
|
**Objective**: Verify grace period limit prevents memory exhaustion
|
|
|
|
**Steps**:
|
|
1. Connect 15 sessions
|
|
2. Disconnect all 15 sessions rapidly (create 15 grace periods)
|
|
3. Verify only 10 grace periods maintained (oldest evicted)
|
|
4. Monitor memory usage (should not spike)
|
|
|
|
**Expected**:
|
|
- ✓ Grace period limit enforced
|
|
- ✓ Oldest entries evicted
|
|
- ✓ No memory exhaustion
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
### Session Settings Tests
|
|
|
|
#### TEST-024: Private Keystrokes
|
|
|
|
**Objective**: Verify keystroke privacy setting works
|
|
|
|
**Steps**:
|
|
1. Enable "Private Keystrokes"
|
|
2. Browser 1 is primary, Browser 2 is observer
|
|
3. Browser 1: Type "test123"
|
|
4. Browser 2: Verify NO keystroke events received
|
|
5. Disable "Private Keystrokes"
|
|
6. Browser 1: Type "test123"
|
|
7. Browser 2: Verify keystroke events received (for awareness)
|
|
|
|
**Expected**:
|
|
- ✓ Private mode blocks keystroke visibility
|
|
- ✓ Non-private mode shows keystrokes
|
|
- ✓ Mouse events always visible
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-025: Configurable Grace Period
|
|
|
|
**Objective**: Verify grace period duration is configurable
|
|
|
|
**Steps**:
|
|
1. Set grace period = 30 seconds
|
|
2. Browser 1 is primary
|
|
3. Close Browser 1
|
|
4. Wait 15 seconds
|
|
5. Reopen Browser 1 → Should reconnect as primary
|
|
6. Close Browser 1 again
|
|
7. Wait 35 seconds
|
|
8. Reopen Browser 1 → Should reconnect as observer
|
|
|
|
**Expected**:
|
|
- ✓ Grace period respects configured duration
|
|
- ✓ Reconnection successful within window
|
|
- ✓ Promotion occurs after expiration
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
### Regression Tests
|
|
|
|
#### TEST-026: No Double Primary
|
|
|
|
**Objective**: Verify system prevents multiple primary sessions
|
|
|
|
**Steps**:
|
|
1. Connect multiple sessions
|
|
2. Perform various transfers and promotions
|
|
3. After each action, verify exactly 1 primary exists
|
|
4. Check system logs for "Multiple primary sessions detected"
|
|
|
|
**Expected**:
|
|
- ✓ Always exactly 1 primary
|
|
- ✓ No double-primary state
|
|
- ✓ Automatic correction if detected
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-027: Orphaned Primary Cleanup
|
|
|
|
**Objective**: Verify orphaned primary IDs are cleaned up
|
|
|
|
**Steps**:
|
|
1. Browser 1 is primary
|
|
2. Simulate crash: Kill browser without proper disconnect
|
|
3. Wait for system to detect disconnect
|
|
4. Verify primary slot cleared
|
|
5. Verify observer promoted
|
|
6. Check no orphaned primary ID remains
|
|
|
|
**Expected**:
|
|
- ✓ Orphaned primary detected
|
|
- ✓ Automatic cleanup
|
|
- ✓ New primary promoted
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
### Performance Tests
|
|
|
|
#### TEST-028: Session List Update Latency
|
|
|
|
**Objective**: Verify session list updates are timely
|
|
|
|
**Steps**:
|
|
1. Have 5 sessions connected
|
|
2. Transfer primary from Browser 1 to Browser 2
|
|
3. Measure time for session list to update on all browsers
|
|
4. Verify update within 500ms
|
|
|
|
**Expected**:
|
|
- ✓ Updates received within 500ms
|
|
- ✓ All sessions updated simultaneously
|
|
- ✓ No stale UI states
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
#### TEST-029: Broadcast Throttling
|
|
|
|
**Objective**: Verify broadcast throttling prevents spam
|
|
|
|
**Steps**:
|
|
1. Rapidly transfer primary 10 times in 1 second
|
|
2. Monitor session list update events
|
|
3. Verify updates throttled (not 10 updates)
|
|
4. Verify final state is correct
|
|
|
|
**Expected**:
|
|
- ✓ Broadcasts throttled to prevent spam
|
|
- ✓ Final state accurate
|
|
- ✓ No performance degradation
|
|
|
|
**Pass Criteria**: All checkmarks verified
|
|
|
|
---
|
|
|
|
### Test Results Template
|
|
|
|
For each test, record results in this format:
|
|
|
|
```
|
|
TEST-XXX: [Test Name]
|
|
Date: YYYY-MM-DD
|
|
Tester: [Name]
|
|
Device: JetKVM [Model]
|
|
Firmware: [Version]
|
|
|
|
Result: PASS / FAIL / BLOCKED
|
|
|
|
Notes:
|
|
- [Any observations]
|
|
- [Deviations from expected behavior]
|
|
- [Performance metrics if applicable]
|
|
|
|
Issues Found:
|
|
- [Issue #1 description]
|
|
- [Issue #2 description]
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
#### Issue: Session stuck in Pending mode
|
|
|
|
**Symptoms**: Session shows "Waiting for approval" indefinitely
|
|
|
|
**Causes**:
|
|
- Primary session disconnected before approving
|
|
- Pending session timeout (1 minute) not expired yet
|
|
|
|
**Solutions**:
|
|
1. Wait for pending timeout (1 minute)
|
|
2. If primary exists, manually approve/deny
|
|
3. If no primary, system will auto-promote after grace period
|
|
|
|
---
|
|
|
|
#### Issue: Cannot send input as primary
|
|
|
|
**Symptoms**: Primary session cannot control mouse/keyboard
|
|
|
|
**Causes**:
|
|
- WebRTC HID channel not established
|
|
- Permission error
|
|
- Browser compatibility issue
|
|
|
|
**Solutions**:
|
|
1. Refresh browser (F5)
|
|
2. Check browser console for errors
|
|
3. Verify primary badge is visible
|
|
4. Try different browser (Chrome recommended)
|
|
|
|
---
|
|
|
|
#### Issue: Video not visible for observer
|
|
|
|
**Symptoms**: Observer sees black screen
|
|
|
|
**Causes**:
|
|
- Permission issue (if pending)
|
|
- WebRTC connection failure
|
|
- Video feed not active
|
|
|
|
**Solutions**:
|
|
1. Verify session mode is Observer (not Pending)
|
|
2. Check WebRTC connection status
|
|
3. Verify HDMI input is active
|
|
4. Refresh browser
|
|
|
|
---
|
|
|
|
#### Issue: Grace period not working
|
|
|
|
**Symptoms**: Primary loses control on reconnect
|
|
|
|
**Causes**:
|
|
- Manual transfer occurred (cleared grace)
|
|
- Grace period expired
|
|
- Session blacklisted
|
|
|
|
**Solutions**:
|
|
1. Check grace period duration setting
|
|
2. Verify reconnection within grace window
|
|
3. Check if session was manually transferred (60s blacklist)
|
|
4. Review logs for grace period events
|
|
|
|
---
|
|
|
|
#### Issue: Emergency promotion selecting wrong session
|
|
|
|
**Symptoms**: Unexpected session becomes primary
|
|
|
|
**Causes**:
|
|
- Trust scoring selected different session
|
|
- Blacklist blocking preferred session
|
|
|
|
**Solutions**:
|
|
1. Review trust score logs
|
|
2. Check blacklist status
|
|
3. Verify session age and properties
|
|
4. Use manual transfer for explicit control
|
|
|
|
---
|
|
|
|
## Configuration Reference
|
|
|
|
### Default Values
|
|
|
|
```json
|
|
{
|
|
"multi_session": {
|
|
"enabled": true,
|
|
"max_sessions": 10,
|
|
"primary_timeout_seconds": 300,
|
|
"allow_cloud_override": true,
|
|
"require_auth_transfer": false
|
|
},
|
|
"session_settings": {
|
|
"requireApproval": false,
|
|
"requireNickname": false,
|
|
"reconnectGrace": 10,
|
|
"primaryTimeout": 300,
|
|
"privateKeystrokes": false,
|
|
"maxRejectionAttempts": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
### Recommended Configurations
|
|
|
|
#### Secure Environment
|
|
|
|
```json
|
|
{
|
|
"session_settings": {
|
|
"requireApproval": true,
|
|
"requireNickname": true,
|
|
"reconnectGrace": 10,
|
|
"primaryTimeout": 300,
|
|
"privateKeystrokes": true,
|
|
"maxRejectionAttempts": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
**Use Case**: High-security environments, data centers, production systems
|
|
|
|
---
|
|
|
|
#### Collaborative Environment
|
|
|
|
```json
|
|
{
|
|
"session_settings": {
|
|
"requireApproval": false,
|
|
"requireNickname": false,
|
|
"reconnectGrace": 30,
|
|
"primaryTimeout": 600,
|
|
"privateKeystrokes": false,
|
|
"maxRejectionAttempts": 5
|
|
}
|
|
}
|
|
```
|
|
|
|
**Use Case**: Team collaboration, pair programming, training
|
|
|
|
---
|
|
|
|
#### Personal Use
|
|
|
|
```json
|
|
{
|
|
"session_settings": {
|
|
"requireApproval": false,
|
|
"requireNickname": false,
|
|
"reconnectGrace": 10,
|
|
"primaryTimeout": 0,
|
|
"privateKeystrokes": false,
|
|
"maxRejectionAttempts": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
**Use Case**: Single user accessing from multiple devices
|
|
|
|
---
|
|
|
|
## Appendix
|
|
|
|
### Session State Diagram
|
|
|
|
```
|
|
[New Connection]
|
|
↓
|
|
┌───────────────────────────────────────┐
|
|
│ Is there a primary? │
|
|
└───────────────────────────────────────┘
|
|
NO ↓ ↓ YES
|
|
[Primary] ┌──────────────────┐
|
|
│ Approval needed? │
|
|
└──────────────────┘
|
|
NO ↓ ↓ YES
|
|
[Observer] [Pending]
|
|
↓
|
|
┌─────────────────┐
|
|
│ Request Control │
|
|
└─────────────────┘
|
|
↓
|
|
[Queued]
|
|
↓
|
|
┌─────────────────┐
|
|
│ Approved/Denied │
|
|
└─────────────────┘
|
|
↓ ↓
|
|
[Primary] [Observer]
|
|
```
|
|
|
|
### Glossary
|
|
|
|
**Session**: A WebRTC connection from a browser to the JetKVM device
|
|
|
|
**Primary**: The session with exclusive control over input and settings
|
|
|
|
**Observer**: A session that can view video but cannot control anything
|
|
|
|
**Queued**: A session that has requested control and is waiting for approval
|
|
|
|
**Pending**: A session waiting for approval to even view video
|
|
|
|
**Grace Period**: Time window for reconnection without losing session state
|
|
|
|
**Blacklist**: Temporary block preventing a session from becoming primary
|
|
|
|
**Transfer**: Changing primary status from one session to another
|
|
|
|
**Emergency Promotion**: Automatic promotion when no primary exists
|
|
|
|
**Trust Score**: Calculated value determining promotion priority
|
|
|
|
---
|
|
|
|
### Changelog
|
|
|
|
**Version 1.0** (Initial Release)
|
|
- Multi-session support with 4 session modes
|
|
- Role-based permissions system
|
|
- Grace period reconnection
|
|
- Session approval workflow
|
|
- Emergency promotion system
|
|
- Transfer protection (blacklisting)
|
|
- Automatic nickname generation
|
|
- Trust-based session selection
|
|
- Configurable timeouts and limits
|
|
|
|
---
|
|
|
|
**Document Version**: 1.0
|
|
**Last Updated**: 2025
|
|
**Maintained By**: JetKVM Development Team
|