1package com.peachpasswords.sync23import kotlinx.serialization.Serializable45private const val MAX_SYNC_ALERTS = 20067@Serializable8enum class SyncAlertType {9 REMOTE_PASSWORD_CHANGE,10 REMOTE_DELETE,11 DEVICE_KEY_ROTATED,12 DEVICE_REVOKED,13}1415@Serializable16data class SyncAlert(17 val type: SyncAlertType,18 val entryId: String? = null,19 val entryTitle: String? = null,20 val deviceName: String? = null,21 val timestamp: Long,22)2324interface SyncAlertRecorder {25 fun record(alerts: List<SyncAlert>)26}2728internal fun boundedSyncAlerts(existing: List<SyncAlert>, incoming: List<SyncAlert>): List<SyncAlert> =29 (existing + incoming).takeLast(MAX_SYNC_ALERTS)3031internal fun isDeviceTrustAlert(alert: SyncAlert): Boolean =32 alert.type == SyncAlertType.DEVICE_KEY_ROTATED ||33 alert.type == SyncAlertType.DEVICE_REVOKED3435internal fun deviceKeyRotationAlert(36 existing: PairedDevice,37 incoming: ManifestDeviceEntry,38 nowMs: Long,39): SyncAlert? {40 val nextLongTerm = incoming.longTermPublicKeyHex.lowercase()41 val nextSigning = incoming.signingPublicKeyHex.lowercase()42 val longTermChanged = existing.longTermPublicKeyHex?.let {43 !CryptoCompare.hex(it, nextLongTerm)44 } == true45 val signingChanged = existing.signingPublicKeyHex?.let {46 !CryptoCompare.hex(it, nextSigning)47 } == true48 if (!longTermChanged && !signingChanged) return null49 50 51 52 53 return SyncAlert(54 type = SyncAlertType.DEVICE_KEY_ROTATED,55 deviceName = existing.deviceName?.takeIf { it.isNotBlank() }56 ?: incoming.deviceName.ifBlank { "paired device" },57 timestamp = nowMs,58 )59}6061fun syncAlertMessage(alert: SyncAlert): String {62 val device = alert.deviceName?.takeIf { it.isNotBlank() } ?: "paired device"63 val title = alert.entryTitle?.takeIf { it.isNotBlank() } ?: "an entry"64 return when (alert.type) {65 SyncAlertType.REMOTE_PASSWORD_CHANGE -> "$device changed the password for $title"66 SyncAlertType.REMOTE_DELETE -> "$device deleted $title"67 SyncAlertType.DEVICE_REVOKED ->68 "$device revoked a paired device"69 SyncAlertType.DEVICE_KEY_ROTATED ->70 "$device's sync key changed - if you didn't reinstall Peach there, rotate your sync identity"71 }72}