Compare commits
No commits in common. "feat/secure-pairing" and "main" have entirely different histories.
feat/secur
...
main
3 changed files with 23 additions and 22 deletions
|
|
@ -115,9 +115,6 @@ data class BeePlugin(
|
||||||
|
|
||||||
data class PairResponse(
|
data class PairResponse(
|
||||||
@SerializedName("serial") val serial: String,
|
@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("version") val version: String,
|
||||||
@SerializedName("ap_ip") val apIp: String,
|
@SerializedName("ap_ip") val apIp: String,
|
||||||
@SerializedName("api_port") val apiPort: Int
|
@SerializedName("api_port") val apiPort: Int
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "varroa_settings")
|
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "varroa_settings")
|
||||||
|
|
||||||
|
|
@ -29,6 +30,16 @@ data class VarroaSettings(
|
||||||
val isPaired: Boolean = false
|
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) {
|
class SettingsDataStore(private val context: Context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -88,11 +99,11 @@ class SettingsDataStore(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store pairing data after a successful /pair. The device returns a RANDOM
|
* Store pairing data after successful pairing with AdaCam.
|
||||||
* token (not derived from the serial) during its one-shot pairing window;
|
* Derives and stores the API token from the serial.
|
||||||
* persist the serial + that token.
|
|
||||||
*/
|
*/
|
||||||
suspend fun savePairing(serial: String, token: String) {
|
suspend fun savePairing(serial: String) {
|
||||||
|
val token = deriveApiToken(serial)
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[KEY_DEVICE_SERIAL] = serial
|
prefs[KEY_DEVICE_SERIAL] = serial
|
||||||
prefs[KEY_API_TOKEN] = token
|
prefs[KEY_API_TOKEN] = token
|
||||||
|
|
|
||||||
|
|
@ -120,22 +120,15 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) {
|
||||||
when (val result = client.pair()) {
|
when (val result = client.pair()) {
|
||||||
is ApiResult.Success -> {
|
is ApiResult.Success -> {
|
||||||
val serial = result.data.serial
|
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")
|
Log.i(TAG, "Pairing successful: serial=$serial")
|
||||||
|
|
||||||
// Persist the RANDOM token the device handed us (not derived)
|
// Store pairing data (derives token automatically)
|
||||||
store.savePairing(serial, token)
|
store.savePairing(serial)
|
||||||
|
|
||||||
// Update client with the new token
|
// Update client with new token
|
||||||
client.apiToken = token
|
val newSettings = store.settings.first()
|
||||||
|
client.apiToken = newSettings.apiToken
|
||||||
|
|
||||||
_pairingResult.value = "Paired successfully! Serial: $serial"
|
_pairingResult.value = "Paired successfully! Serial: $serial"
|
||||||
|
|
||||||
// Fetch statuses now that we're paired
|
// Fetch statuses now that we're paired
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue