diff --git a/app/src/main/java/com/adamaps/varroa/data/Models.kt b/app/src/main/java/com/adamaps/varroa/data/Models.kt index c50838b..18faff5 100644 --- a/app/src/main/java/com/adamaps/varroa/data/Models.kt +++ b/app/src/main/java/com/adamaps/varroa/data/Models.kt @@ -115,9 +115,6 @@ data class BeePlugin( data class PairResponse( @SerializedName("serial") val serial: String, - // Random device API token, returned once during the pairing window. Nullable - // so an old/closed-window device (no token field / 403) is handled gracefully. - @SerializedName("token") val token: String? = null, @SerializedName("version") val version: String, @SerializedName("ap_ip") val apIp: String, @SerializedName("api_port") val apiPort: Int diff --git a/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt b/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt index e6bac59..6d9adcc 100644 --- a/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt +++ b/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt @@ -10,6 +10,7 @@ import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import java.security.MessageDigest private val Context.dataStore: DataStore by preferencesDataStore(name = "varroa_settings") @@ -29,6 +30,16 @@ data class VarroaSettings( val isPaired: Boolean = false ) +/** + * Derive the API token from the device serial. + * Token = first 32 chars of SHA-256("adacam-api-{serial}-token") + */ +fun deriveApiToken(serial: String): String { + val input = "adacam-api-$serial-token" + val bytes = MessageDigest.getInstance("SHA-256").digest(input.toByteArray()) + return bytes.joinToString("") { "%02x".format(it) }.substring(0, 32) +} + class SettingsDataStore(private val context: Context) { companion object { @@ -88,11 +99,11 @@ class SettingsDataStore(private val context: Context) { } /** - * Store pairing data after a successful /pair. The device returns a RANDOM - * token (not derived from the serial) during its one-shot pairing window; - * persist the serial + that token. + * Store pairing data after successful pairing with AdaCam. + * Derives and stores the API token from the serial. */ - suspend fun savePairing(serial: String, token: String) { + suspend fun savePairing(serial: String) { + val token = deriveApiToken(serial) context.dataStore.edit { prefs -> prefs[KEY_DEVICE_SERIAL] = serial prefs[KEY_API_TOKEN] = token diff --git a/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt index 42de564..8e16e29 100644 --- a/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt @@ -120,22 +120,15 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) { when (val result = client.pair()) { is ApiResult.Success -> { val serial = result.data.serial - val token = result.data.token - if (token.isNullOrBlank()) { - // No token = pairing window closed on the device (or old build). - Log.w(TAG, "Pairing returned no token (window closed?)") - _pairingResult.value = - "Pairing window closed. Run 'adacam-pair' on the Bee, then pair again." - return@launch - } Log.i(TAG, "Pairing successful: serial=$serial") - - // Persist the RANDOM token the device handed us (not derived) - store.savePairing(serial, token) - - // Update client with the new token - client.apiToken = token - + + // Store pairing data (derives token automatically) + store.savePairing(serial) + + // Update client with new token + val newSettings = store.settings.first() + client.apiToken = newSettings.apiToken + _pairingResult.value = "Paired successfully! Serial: $serial" // Fetch statuses now that we're paired