Make player token reset non-breaking (deferred rotation)
The maintenance-page reset used to rotate auth_code immediately. Because the token also guards the playlist endpoints, that stopped a player from syncing content the instant the button was pressed -- before the player ever had a chance to react -- and clients that do not understand the heartbeat's 401 auth_required would have stayed broken until redeployed. Split the model behaviour: * request_reauth() -- sets the flag only (used by the admin reset endpoints and the automatic interval). The current token stays valid, so there is no outage while the player is being asked to re-authenticate. * issue_new_token() -- replaces the token now (used by the existing "regenerate auth code" action, where the admin is shown the new value). * mark_authenticated() -- if a reset was pending, this is where the old token is retired: a new token is issued and returned to the player in the /api/auth/player response. Net effect: the reset is a request the player acts on, never a forced logout. A player that ignores it keeps working and shows as "awaiting re-auth" in the UI; one that handles it gets a fresh token and the old one stops working. Maintenance page wording updated to match (request vs reset; "awaiting re-auth" badge), plus a themed info note explaining it is not a forced logout. Adds docs/player-auth-reset.md describing the protocol and exactly what the player client (Kiwy-Signage) still needs to implement: detect 401 / auth_required on the heartbeat (and on the token-guarded endpoints), re-run POST /api/auth/player with the stored quick-connect code, save the new auth_code and retry. No password or SSH redeploy required. Verified end-to-end: token unchanged and still accepted after the request (200), heartbeat answers 401 auth_required=token_reset, re-auth issues a different token, the old token is then rejected (403) and the new one accepted (200), flag cleared; the interval path only flags without rotating.
This commit is contained in:
@@ -27,6 +27,23 @@
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
/* Informational note (e.g. the "this is a request, not a forced logout"
|
||||
explanation), themed so it stays readable in both modes. */
|
||||
.maint-note {
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.875rem;
|
||||
background: #e7f3ff;
|
||||
border-left: 4px solid #0066cc;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
body.dark-mode .maint-note {
|
||||
background: #1e3a5f;
|
||||
border-left-color: #90cdf4;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
body.dark-mode .maint-badge-pending {
|
||||
background: #4a3800;
|
||||
color: #fbbf24;
|
||||
@@ -93,9 +110,16 @@
|
||||
<!-- ================= Player Authentication Tokens ================= -->
|
||||
<div class="card" style="margin-bottom: 30px;">
|
||||
<h2>🔑 Player Authentication Tokens</h2>
|
||||
<p style="color: #6c757d; margin-bottom: 20px;">
|
||||
Resetting a token invalidates the player's current credentials. On its next
|
||||
heartbeat the player is told to authenticate again and receives a fresh token.
|
||||
<p style="color: #6c757d; margin-bottom: 12px;">
|
||||
Requests that a player authenticate again. The player is told on its next
|
||||
heartbeat and receives a fresh token when it does — its current token stays
|
||||
valid until then, so playlist syncing is not interrupted.
|
||||
</p>
|
||||
<p class="maint-note" style="margin-bottom: 20px;">
|
||||
ℹ️ This asks the player to re-authenticate; it is not a forced logout. A player
|
||||
whose software does not yet handle the request keeps working on its existing
|
||||
token and simply stays <em>awaiting re-auth</em> below. Players do not need
|
||||
a password — the quick-connect code they already hold is enough.
|
||||
</p>
|
||||
|
||||
<form method="POST" action="{{ url_for('admin.maintenance_set_interval') }}"
|
||||
@@ -124,9 +148,9 @@
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ url_for('admin.maintenance_reset_all_tokens') }}"
|
||||
onsubmit="return confirm('Reset the authentication token for ALL players? Each player will re-authenticate on its next heartbeat.');"
|
||||
onsubmit="return confirm('Ask ALL players to re-authenticate? Each one will be told on its next heartbeat and then receive a new token. Their current tokens keep working until then.');"
|
||||
style="margin-bottom: 20px;">
|
||||
<button type="submit" class="btn btn-danger">🔁 Reset All Player Tokens</button>
|
||||
<button type="submit" class="btn btn-danger">🔁 Request Re-auth for All Players</button>
|
||||
</form>
|
||||
|
||||
<div style="max-height: 460px; overflow-y: auto;">
|
||||
@@ -169,7 +193,7 @@
|
||||
</td>
|
||||
<td style="padding: 10px;">
|
||||
{% if player.auth_reset_required %}
|
||||
<span class="maint-badge-pending">pending</span>
|
||||
<span class="maint-badge-pending" title="The player has been asked to re-authenticate; the token is replaced once it does.">awaiting re-auth</span>
|
||||
{% else %}
|
||||
<span style="color: var(--text-secondary); font-size: 12px;">—</span>
|
||||
{% endif %}
|
||||
@@ -178,9 +202,9 @@
|
||||
<form method="POST"
|
||||
action="{{ url_for('admin.maintenance_reset_token', player_id=player.id) }}"
|
||||
style="display: inline;"
|
||||
onsubmit="return confirm('Reset the token for {{ player.name }}?');">
|
||||
onsubmit="return confirm('Ask {{ player.name }} to re-authenticate?');">
|
||||
<button type="submit" class="btn btn-warning btn-sm"
|
||||
style="padding: 4px 10px; font-size: 12px;">🔁 Reset</button>
|
||||
style="padding: 4px 10px; font-size: 12px;">🔁 Re-auth</button>
|
||||
</form>
|
||||
{% if player.auth_reset_required %}
|
||||
<form method="POST"
|
||||
|
||||
Reference in New Issue
Block a user