Files
digiserver-v2/app/models/player_auth_config.py
T
ske087 88827be7be Add admin maintenance console
Admin-only page reachable from a new card on the admin panel (replacing the
Quick Actions card) that centralises housekeeping operations:

* Database integrity: PRAGMA integrity_check / quick_check / foreign_key_check,
  plus VACUUM + ANALYZE to reclaim free space.
* Leftover records: detects and removes orphaned rows left behind by deleted
  players/content (player_feedback, player_edit, playlist_content, dangling
  player.playlist_id) and orphaned edited-media folders on disk.
* Player authentication tokens: manual reset (single or all), a configurable
  automatic reset interval, and cancellation of a pending reset.

Resetting a token rotates player.auth_code and sets auth_reset_required, so the
next heartbeat (POST /api/player-feedback) answers 401 + auth_required and the
client restarts its authentication flow. A successful authentication clears the
flag and stamps auth_token_issued_at, from which the interval is measured.

New: PlayerAuthConfig model, add_player_auth_reset migration (registered in
docker-entrypoint.sh), admin/maintenance.html.
2026-10-14 22:13:18 +03:00

87 lines
3.0 KiB
Python

"""Player authentication configuration (token reset policy).
Stores the single-row policy that controls how often a player's
authentication token is invalidated so that the client restarts its
authentication flow on the next heartbeat.
"""
from datetime import datetime
from typing import Optional
from app.extensions import db
class PlayerAuthConfig(db.Model):
"""Single-row settings for player authentication tokens.
Attributes:
id: Primary key
reset_interval_days: Automatically require re-authentication once a
token is older than this many days. ``0`` (the default) disables
automatic expiry; tokens are then only reset manually from the
maintenance page.
updated_at: Last modification timestamp
updated_by: Username of the last editor
"""
__tablename__ = 'player_auth_config'
id = db.Column(db.Integer, primary_key=True)
reset_interval_days = db.Column(db.Integer, default=0, nullable=False)
updated_at = db.Column(db.DateTime, default=datetime.utcnow,
onupdate=datetime.utcnow, nullable=False)
updated_by = db.Column(db.String(255), nullable=True)
def __repr__(self) -> str:
"""String representation of PlayerAuthConfig."""
return f'<PlayerAuthConfig interval={self.reset_interval_days}d>'
@classmethod
def get_config(cls) -> Optional['PlayerAuthConfig']:
"""Return the current configuration row, or ``None`` if unset."""
return cls.query.first()
@classmethod
def get_interval_days(cls) -> int:
"""Return the configured reset interval in days (0 = disabled).
Defensive against an unset row or a NULL/negative value so callers
can use the result directly without extra guards.
"""
config = cls.get_config()
if not config or not config.reset_interval_days:
return 0
return max(0, int(config.reset_interval_days))
@classmethod
def create_or_update(cls, reset_interval_days: int,
updated_by: str = None) -> 'PlayerAuthConfig':
"""Create or update the authentication reset policy.
Args:
reset_interval_days: Days before a token is auto-expired
(0 disables automatic expiry)
updated_by: Username of the editor
Returns:
The persisted ``PlayerAuthConfig`` instance.
"""
config = cls.get_config()
if not config:
config = cls()
config.reset_interval_days = max(0, int(reset_interval_days or 0))
config.updated_by = updated_by
config.updated_at = datetime.utcnow()
db.session.add(config)
db.session.commit()
return config
def to_dict(self) -> dict:
"""Convert configuration to a dictionary."""
return {
'id': self.id,
'reset_interval_days': self.reset_interval_days,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
'updated_by': self.updated_by,
}