382 lines
9.0 KiB
Markdown
382 lines
9.0 KiB
Markdown
# Player Authentication System - DigiServer v2 & Kiwy-Signage
|
|
|
|
## Overview
|
|
|
|
DigiServer v2 now includes a secure player authentication system compatible with Kiwy-Signage players. Players can authenticate using either a password or quick connect code, and their credentials are securely stored locally.
|
|
|
|
## Features
|
|
|
|
✅ **Dual Authentication Methods**
|
|
- Password-based authentication (secure bcrypt hashing)
|
|
- Quick Connect codes for easy pairing
|
|
|
|
✅ **Secure Credential Storage**
|
|
- Auth codes saved locally in encrypted configuration
|
|
- No need to re-authenticate on every restart
|
|
|
|
✅ **Automatic Session Management**
|
|
- Auth codes persist across player restarts
|
|
- Automatic status updates and heartbeats
|
|
|
|
✅ **Player Identification**
|
|
- Unique hostname for each player
|
|
- Configurable display orientation (Landscape/Portrait)
|
|
|
|
## Database Schema
|
|
|
|
The Player model now includes:
|
|
|
|
```python
|
|
class Player(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(255), nullable=False)
|
|
hostname = db.Column(db.String(255), unique=True, nullable=False, index=True)
|
|
location = db.Column(db.String(255), nullable=True)
|
|
auth_code = db.Column(db.String(255), unique=True, nullable=False, index=True)
|
|
password_hash = db.Column(db.String(255), nullable=False)
|
|
quickconnect_code = db.Column(db.String(255), nullable=True)
|
|
group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=True)
|
|
orientation = db.Column(db.String(16), default='Landscape')
|
|
status = db.Column(db.String(50), default='offline')
|
|
last_seen = db.Column(db.DateTime, nullable=True)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
```
|
|
|
|
## API Endpoints
|
|
|
|
### 1. Player Authentication
|
|
**POST** `/api/auth/player`
|
|
|
|
Authenticate a player and receive auth code.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"hostname": "player-001",
|
|
"password": "your_password",
|
|
"quickconnect_code": "QUICK123" // Optional if using password
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"player_id": 1,
|
|
"player_name": "Demo Player",
|
|
"hostname": "player-001",
|
|
"auth_code": "abc123xyz...",
|
|
"group_id": 5,
|
|
"orientation": "Landscape",
|
|
"status": "online"
|
|
}
|
|
```
|
|
|
|
**Error Response (401 Unauthorized):**
|
|
```json
|
|
{
|
|
"error": "Invalid credentials"
|
|
}
|
|
```
|
|
|
|
### 2. Verify Auth Code
|
|
**POST** `/api/auth/verify`
|
|
|
|
Verify an existing auth code.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"auth_code": "abc123xyz..."
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"valid": true,
|
|
"player_id": 1,
|
|
"player_name": "Demo Player",
|
|
"hostname": "player-001",
|
|
"group_id": 5,
|
|
"orientation": "Landscape",
|
|
"status": "online"
|
|
}
|
|
```
|
|
|
|
## Player Configuration File
|
|
|
|
Players store their configuration in `player_config.ini`:
|
|
|
|
```ini
|
|
[server]
|
|
server_url = http://your-server:5000
|
|
|
|
[player]
|
|
hostname = player-001
|
|
auth_code = abc123xyz...
|
|
player_id = 1
|
|
group_id = 5
|
|
|
|
[display]
|
|
orientation = Landscape
|
|
resolution = 1920x1080
|
|
|
|
[security]
|
|
verify_ssl = true
|
|
timeout = 30
|
|
|
|
[cache]
|
|
cache_dir = ./cache
|
|
max_cache_size = 1024
|
|
|
|
[logging]
|
|
enabled = true
|
|
log_level = INFO
|
|
log_file = ./player.log
|
|
```
|
|
|
|
## Integration with Kiwy-Signage
|
|
|
|
### Step 1: Copy Authentication Module
|
|
|
|
Copy `player_auth_module.py` to your Kiwy-Signage project:
|
|
|
|
```bash
|
|
cp digiserver-v2/player_auth_module.py signage-player/src/player_auth.py
|
|
```
|
|
|
|
### Step 2: Initialize Authentication
|
|
|
|
In your main signage player code:
|
|
|
|
```python
|
|
from player_auth import PlayerAuth
|
|
|
|
# Initialize authentication
|
|
auth = PlayerAuth(config_path='player_config.ini')
|
|
|
|
# Check if already authenticated
|
|
if auth.is_authenticated():
|
|
# Verify saved credentials
|
|
valid, info = auth.verify_auth()
|
|
if valid:
|
|
print(f"Authenticated as: {info['player_name']}")
|
|
else:
|
|
# Re-authenticate
|
|
success, error = auth.authenticate(
|
|
hostname='player-001',
|
|
password='your_password'
|
|
)
|
|
else:
|
|
# First time setup
|
|
hostname = input("Enter player hostname: ")
|
|
password = input("Enter password: ")
|
|
|
|
success, error = auth.authenticate(hostname, password)
|
|
if success:
|
|
print("Authentication successful!")
|
|
else:
|
|
print(f"Authentication failed: {error}")
|
|
```
|
|
|
|
### Step 3: Use Authentication for API Calls
|
|
|
|
```python
|
|
# Get playlist with authentication
|
|
playlist = auth.get_playlist()
|
|
|
|
# Send heartbeat
|
|
auth.send_heartbeat(status='online')
|
|
|
|
# Make authenticated API request
|
|
import requests
|
|
|
|
auth_code = auth.get_auth_code()
|
|
player_id = auth.config.get('player', 'player_id')
|
|
server_url = auth.get_server_url()
|
|
|
|
response = requests.get(
|
|
f"{server_url}/api/playlists/{player_id}",
|
|
headers={'Authorization': f'Bearer {auth_code}'}
|
|
)
|
|
```
|
|
|
|
## Server Setup
|
|
|
|
### 1. Create Players in DigiServer
|
|
|
|
Via Web Interface:
|
|
1. Log in as admin (admin/admin123)
|
|
2. Navigate to Players → Add Player
|
|
3. Fill in:
|
|
- **Name**: Display name
|
|
- **Hostname**: Unique identifier (e.g., `player-001`)
|
|
- **Location**: Physical location
|
|
- **Password**: Secure password
|
|
- **Quick Connect Code**: Optional easy pairing code
|
|
- **Orientation**: Landscape or Portrait
|
|
|
|
Via Python:
|
|
```python
|
|
from app.extensions import db
|
|
from app.models import Player
|
|
import secrets
|
|
|
|
player = Player(
|
|
name='Office Player',
|
|
hostname='office-player-001',
|
|
location='Main Office - Reception',
|
|
auth_code=secrets.token_urlsafe(32),
|
|
orientation='Landscape'
|
|
)
|
|
player.set_password('secure_password_123')
|
|
player.set_quickconnect_code('OFFICE123')
|
|
|
|
db.session.add(player)
|
|
db.session.commit()
|
|
```
|
|
|
|
### 2. Distribute Credentials
|
|
|
|
Securely provide each player with:
|
|
- Server URL
|
|
- Hostname
|
|
- Password OR Quick Connect Code
|
|
|
|
## Security Considerations
|
|
|
|
✅ **Passwords**: Hashed with bcrypt (cost factor 12)
|
|
✅ **Auth Codes**: 32-byte URL-safe tokens
|
|
✅ **HTTPS**: Enable SSL in production
|
|
✅ **Rate Limiting**: API endpoints protected (10 req/min for auth)
|
|
✅ **Local Storage**: Config file permissions should be 600
|
|
|
|
## Troubleshooting
|
|
|
|
### Player Can't Authenticate
|
|
|
|
1. Check server connectivity:
|
|
```bash
|
|
curl http://your-server:5000/api/health
|
|
```
|
|
|
|
2. Verify credentials in database
|
|
3. Check server logs for authentication attempts
|
|
4. Ensure hostname is unique
|
|
|
|
### Auth Code Invalid
|
|
|
|
1. Clear saved config: `rm player_config.ini`
|
|
2. Re-authenticate with password
|
|
3. Check if player was deleted from server
|
|
|
|
### Connection Timeout
|
|
|
|
1. Increase timeout in `player_config.ini`:
|
|
```ini
|
|
[security]
|
|
timeout = 60
|
|
```
|
|
|
|
2. Check network connectivity
|
|
3. Verify server is running
|
|
|
|
## Migration from v1
|
|
|
|
If migrating from DigiServer v1:
|
|
|
|
1. **Export player data** from v1 database
|
|
2. **Create players** in v2 with hostname = old username
|
|
3. **Set passwords** using `player.set_password()`
|
|
4. **Update player apps** with new authentication module
|
|
5. **Test authentication** before full deployment
|
|
|
|
## Example: Complete Player Setup
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
"""
|
|
Complete player setup example
|
|
"""
|
|
from player_auth import PlayerAuth
|
|
import sys
|
|
|
|
def setup_player():
|
|
"""Interactive player setup"""
|
|
auth = PlayerAuth()
|
|
|
|
# Check if already configured
|
|
if auth.is_authenticated():
|
|
print(f"✅ Already configured as: {auth.get_hostname()}")
|
|
|
|
# Test connection
|
|
valid, info = auth.verify_auth()
|
|
if valid:
|
|
print(f"✅ Connection successful")
|
|
print(f" Player: {info['player_name']}")
|
|
print(f" Group: {info.get('group_id', 'None')}")
|
|
return True
|
|
else:
|
|
print("❌ Saved credentials invalid, reconfiguring...")
|
|
auth.clear_auth()
|
|
|
|
# First time setup
|
|
print("\n🚀 Player Setup")
|
|
print("-" * 50)
|
|
|
|
# Get server URL
|
|
server_url = input("Server URL [http://localhost:5000]: ").strip()
|
|
if server_url:
|
|
auth.config['server']['server_url'] = server_url
|
|
auth.save_config()
|
|
|
|
# Get hostname
|
|
hostname = input("Player hostname: ").strip()
|
|
if not hostname:
|
|
print("❌ Hostname required")
|
|
return False
|
|
|
|
# Authentication method
|
|
print("\nAuthentication method:")
|
|
print("1. Password")
|
|
print("2. Quick Connect Code")
|
|
choice = input("Choice [1]: ").strip() or "1"
|
|
|
|
if choice == "1":
|
|
password = input("Password: ").strip()
|
|
success, error = auth.authenticate(hostname, password=password)
|
|
else:
|
|
code = input("Quick Connect Code: ").strip()
|
|
success, error = auth.authenticate(hostname, quickconnect_code=code)
|
|
|
|
if success:
|
|
print("\n✅ Authentication successful!")
|
|
print(f" Config saved to: {auth.config_path}")
|
|
return True
|
|
else:
|
|
print(f"\n❌ Authentication failed: {error}")
|
|
return False
|
|
|
|
if __name__ == '__main__':
|
|
if setup_player():
|
|
sys.exit(0)
|
|
else:
|
|
sys.exit(1)
|
|
```
|
|
|
|
## Files Included
|
|
|
|
- `player_auth_module.py` - Python authentication module for players
|
|
- `player_config_template.ini` - Configuration template
|
|
- `reinit_db.sh` - Script to recreate database with new schema
|
|
- `PLAYER_AUTH.md` - This documentation
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
1. Check server logs: `app/instance/logs/`
|
|
2. Check player logs: `player.log`
|
|
3. Verify API health: `/api/health`
|
|
4. Review authentication attempts in server logs
|