mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
Android + web player: handle device_token authentication
Follows up on the security audit remediation (afbe113) which added
device_token auth to the WebSocket /device namespace.
Android player (ServerConfig.kt, WebSocketService.kt):
- Persist device_token in EncryptedSharedPreferences alongside device_id
- Send device_token in device:register on reconnect and playlist refresh
- Save/overwrite token from device:registered response (handles legacy
devices getting their first token)
- Handle device:auth-error by clearing credentials and showing pairing screen
- clearDeviceCredentials() method wipes device_id, device_token, is_paired
Web player (player/index.html):
- Save deviceToken in localStorage config from device:registered response
- Send device_token in register() payload on reconnect
- Handle device:auth-error and device:unpaired events — clear config and
show re-pair UI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
afbe113acf
commit
1d253c4cae
|
|
@ -33,6 +33,10 @@ class ServerConfig(context: Context) {
|
|||
get() = prefs.getString("device_id", "") ?: ""
|
||||
set(value) = prefs.edit().putString("device_id", value).apply()
|
||||
|
||||
var deviceToken: String
|
||||
get() = prefs.getString("device_token", "") ?: ""
|
||||
set(value) = prefs.edit().putString("device_token", value).apply()
|
||||
|
||||
var deviceName: String
|
||||
get() = prefs.getString("device_name", "Unnamed Display") ?: "Unnamed Display"
|
||||
set(value) = prefs.edit().putString("device_name", value).apply()
|
||||
|
|
@ -47,6 +51,14 @@ class ServerConfig(context: Context) {
|
|||
prefs.edit().putBoolean("is_paired", paired).apply()
|
||||
}
|
||||
|
||||
fun clearDeviceCredentials() {
|
||||
prefs.edit()
|
||||
.remove("device_id")
|
||||
.remove("device_token")
|
||||
.remove("is_paired")
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
prefs.edit().clear().apply()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,15 +102,25 @@ class WebSocketService : Service() {
|
|||
val data = args[0] as JSONObject
|
||||
val newDeviceId = data.getString("device_id")
|
||||
config.deviceId = newDeviceId
|
||||
// Persist device_token (issued on first register, or refreshed on reconnect)
|
||||
if (data.has("device_token")) {
|
||||
config.deviceToken = data.getString("device_token")
|
||||
}
|
||||
Log.i("WebSocketService", "Registered as: $newDeviceId")
|
||||
handler.post { onRegistered?.invoke(newDeviceId) }
|
||||
startHeartbeat()
|
||||
}
|
||||
|
||||
on("device:unpaired") {
|
||||
Log.w("WebSocketService", "Device not found on server - clearing config")
|
||||
config.setPaired(false)
|
||||
config.deviceId = ""
|
||||
Log.w("WebSocketService", "Device not found on server - clearing credentials")
|
||||
config.clearDeviceCredentials()
|
||||
handler.post { onUnpaired?.invoke() }
|
||||
}
|
||||
|
||||
on("device:auth-error") { args ->
|
||||
val msg = (args.firstOrNull() as? JSONObject)?.optString("error", "Authentication failed") ?: "Authentication failed"
|
||||
Log.w("WebSocketService", "Device auth rejected: $msg — clearing credentials for re-pair")
|
||||
config.clearDeviceCredentials()
|
||||
handler.post { onUnpaired?.invoke() }
|
||||
}
|
||||
|
||||
|
|
@ -234,6 +244,11 @@ class WebSocketService : Service() {
|
|||
val data = JSONObject().apply {
|
||||
if (config.isProvisioned && config.isPaired) {
|
||||
put("device_id", config.deviceId)
|
||||
// Send device_token for authentication (may be empty for legacy devices)
|
||||
val token = config.deviceToken
|
||||
if (token.isNotEmpty()) {
|
||||
put("device_token", token)
|
||||
}
|
||||
} else {
|
||||
// Generate a pairing code if we don't have one
|
||||
val pairingCode = (100000..999999).random().toString()
|
||||
|
|
@ -279,6 +294,10 @@ class WebSocketService : Service() {
|
|||
// Re-register triggers the server to send current playlist
|
||||
val data = org.json.JSONObject().apply {
|
||||
put("device_id", config.deviceId)
|
||||
val token = config.deviceToken
|
||||
if (token.isNotEmpty()) {
|
||||
put("device_token", token)
|
||||
}
|
||||
put("device_info", deviceInfo.getDeviceInfo())
|
||||
}
|
||||
socket?.emit("device:register", data)
|
||||
|
|
|
|||
|
|
@ -253,6 +253,7 @@
|
|||
|
||||
socket.on('device:registered', (data) => {
|
||||
config.deviceId = data.device_id;
|
||||
if (data.device_token) config.deviceToken = data.device_token;
|
||||
saveConfig(config);
|
||||
console.log('Registered:', data.device_id);
|
||||
|
||||
|
|
@ -278,6 +279,30 @@
|
|||
showStatus('Waiting for content...');
|
||||
});
|
||||
|
||||
socket.on('device:unpaired', () => {
|
||||
console.warn('Device not found on server — clearing credentials');
|
||||
delete config.deviceId;
|
||||
delete config.deviceToken;
|
||||
config.paired = false;
|
||||
saveConfig(config);
|
||||
document.getElementById('setupScreen').style.display = 'flex';
|
||||
document.getElementById('urlForm').style.display = 'block';
|
||||
document.getElementById('pairingSection').style.display = 'none';
|
||||
document.getElementById('setupStatus').textContent = 'Device was removed from server. Please reconnect.';
|
||||
});
|
||||
|
||||
socket.on('device:auth-error', (data) => {
|
||||
console.warn('Device auth rejected:', data?.error || 'unknown');
|
||||
delete config.deviceId;
|
||||
delete config.deviceToken;
|
||||
config.paired = false;
|
||||
saveConfig(config);
|
||||
document.getElementById('setupScreen').style.display = 'flex';
|
||||
document.getElementById('urlForm').style.display = 'block';
|
||||
document.getElementById('pairingSection').style.display = 'none';
|
||||
document.getElementById('setupStatus').textContent = 'Authentication failed. Please re-pair this device.';
|
||||
});
|
||||
|
||||
socket.on('device:playlist-update', (data) => {
|
||||
console.log('Playlist update:', data.assignments?.length, 'items');
|
||||
handlePlaylistUpdate(data);
|
||||
|
|
@ -361,6 +386,7 @@
|
|||
const data = {};
|
||||
if (config.deviceId && config.paired) {
|
||||
data.device_id = config.deviceId;
|
||||
if (config.deviceToken) data.device_token = config.deviceToken;
|
||||
} else {
|
||||
const code = String(Math.floor(100000 + Math.random() * 900000));
|
||||
config.pairingCode = code;
|
||||
|
|
|
|||
Loading…
Reference in a new issue