updated digiserver 2
This commit is contained in:
381
PLAYER_AUTH.md
Normal file
381
PLAYER_AUTH.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user