# Player authentication & token re-authentication Server-side implementation reference for the player client (`Kiwy-Signage`, https://gitea.moto-adv.com/ske087/Kiwy-Signage.git). This document describes how the DigiServer side behaves so the player can be updated to match. **The server side is complete; the client still needs to handle the re-authentication request** (see "What the player must implement"). --- ## 1. How a player authenticates today The player reads `config/app_config.json` (written by the SSH deploy step), which contains: ```json { "server_ip": "192.168.0.159", "port": "443", "screen_name": "rpi-test1", // == Player.hostname on the server "quickconnect_key": "AB12CD", // == Player.quickconnect_code (plaintext) "orientation": "Landscape", "use_https": true, "verify_ssl": false } ``` Note the player builds URLs as `{scheme}://{server_ip}:{port}/api/...` — it does **not** use any path prefix. ### Authentication flow (documented in `docs/05-blueprints-api.md`) ```mermaid sequenceDiagram participant P as Player participant A as DigiServer P->>A: POST /api/auth/player {hostname, quickconnect_code} A-->>P: {auth_code, player_id, playlist_id, orientation} Note over P: stores auth_code (player_auth.json) P->>A: GET /api/playlists/ (Bearer auth_code) A-->>P: playlist + playlist_version loop refresh poll P->>A: GET /api/playlist-version/ (Bearer auth_code) A-->>P: {playlist_version, content_count} end loop heartbeat (~15s) P->>A: POST /api/player-feedback {hostname, status, ...} A-->>P: {success: true} end ``` ### Which endpoints use the token | Endpoint | Auth | |---|---| | `POST /api/auth/player` | hostname + password **or** quickconnect code (no token) | | `GET /api/playlists?hostname=&quickconnect_code=` | quickconnect code (no token) | | `GET /api/playlists/` | **Bearer `auth_code`** | | `GET /api/playlist-version/` | **Bearer `auth_code`** | | `POST /api/player-edit-media` | **Bearer `auth_code`** | | `POST /api/player-feedback` (heartbeat) | none — the player is looked up by hostname | Because the token guards the playlist endpoints, **an invalid token stops content sync**, not just auth. --- ## 2. Re-authentication request (what changed on the server) An admin can request re-authentication from **Admin → Maintenance → Player Authentication Tokens**, either per player or for all players. There is also an optional automatic interval (days) after which the request fires on its own. A re-authentication request is **deferred** — the server does *not* invalidate the current token: ```mermaid sequenceDiagram participant Admin participant A as DigiServer participant P as Player Admin->>A: request re-auth A->>A: auth_reset_required = true (token unchanged) P->>A: POST /api/player-feedback A-->>P: 401 {auth_required: true, reason: "token_reset"} Note over P: ⚠️ CLIENT ACTION REQUIRED HERE P->>A: POST /api/auth/player {hostname, quickconnect_code} A->>A: issue NEW token, clear flag A-->>P: {auth_code: NEW} Note over P: store new auth_code, retry requests P->>A: GET /api/playlists/ (Bearer NEW) ✅ ``` Key points: * Until the player re-authenticates, its **old token remains valid** — so a player that ignores the request keeps syncing content (no outage). * The new token is issued **during** `POST /api/auth/player`, and returned in the `auth_code` field of the response, exactly like first-time setup. * The player needs **no password**: the `quickconnect_key` already in `app_config.json` is sufficient. * No SSH, restart, or redeploy is required. * The admin UI shows the player as **awaiting re-auth** until this completes; the admin can also **Cancel** the request. --- ## 3. What the player must implement Today, if the client never handles the request, nothing breaks — but the reset never happens (the player keeps its old token forever and the admin UI stays on "awaiting re-auth"). To make the reset effective, the player client should: 1. **Detect the request.** On the heartbeat response, treat `HTTP 401` with `auth_required: true` as "re-authenticate", e.g.: ```python resp = requests.post(f"{base}/api/player-feedback", json=payload, timeout=10) if resp.status_code == 401: body = resp.json() if resp.content else {} if body.get("auth_required"): reauthenticate(reason=body.get("reason")) # "token_reset" | "token_expired" return ``` 2. **Also handle 401 on the token-guarded calls.** If the token is ever rejected by `/api/playlists/` or `/api/playlist-version/`, run the same re-authentication path. This makes the client self-healing even if the heartbeat was missed. 3. **Re-authenticate using the stored quick-connect code:** ```python def reauthenticate(): cfg = load_app_config() # config/app_config.json r = requests.post(f"{base}/api/auth/player", json={ "hostname": cfg["screen_name"], "quickconnect_code": cfg["quickconnect_key"], }, timeout=15) r.raise_for_status() save_auth(r.json()["auth_code"]) # player_auth.json # then resume: fetch playlist + version with the new token ``` 4. **Retry the failed request** with the new token so sync resumes seamlessly. 5. **Be idempotent / back off.** While a request is pending the heartbeat keeps answering `401`; avoid a tight retry loop (e.g. re-authenticate at most once per N seconds, or immediately then resume the normal heartbeat cadence). ### Recommended: also add `re-auth` to the edit-media path `POST /api/player-edit-media` is token-guarded too, so the same 401 → re-authenticate → retry wrapper should apply there. --- ## 4. Manual alternative (no client change needed) Redeploying the player via **Admin → Build Player Files / Deploy** rewrites `config/app_config.json` and deletes `player_auth.json`, which forces a clean authentication on next start. This is the deterministic path and is what the maintenance reset is intended to replace (it is remote and non-disruptive).