Compare commits
1 commit
main
...
feat/secur
| Author | SHA1 | Date | |
|---|---|---|---|
| d4bb3e0363 |
3 changed files with 22 additions and 23 deletions
|
|
@ -115,6 +115,9 @@ 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
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ 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<Preferences> by preferencesDataStore(name = "varroa_settings")
|
||||
|
||||
|
|
@ -30,16 +29,6 @@ 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 {
|
||||
|
|
@ -99,11 +88,11 @@ class SettingsDataStore(private val context: Context) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Store pairing data after successful pairing with AdaCam.
|
||||
* Derives and stores the API token from the serial.
|
||||
* 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.
|
||||
*/
|
||||
suspend fun savePairing(serial: String) {
|
||||
val token = deriveApiToken(serial)
|
||||
suspend fun savePairing(serial: String, token: String) {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[KEY_DEVICE_SERIAL] = serial
|
||||
prefs[KEY_API_TOKEN] = token
|
||||
|
|
|
|||
|
|
@ -120,15 +120,22 @@ 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")
|
||||
|
||||
// Store pairing data (derives token automatically)
|
||||
store.savePairing(serial)
|
||||
|
||||
// Update client with new token
|
||||
val newSettings = store.settings.first()
|
||||
client.apiToken = newSettings.apiToken
|
||||
|
||||
|
||||
// Persist the RANDOM token the device handed us (not derived)
|
||||
store.savePairing(serial, token)
|
||||
|
||||
// Update client with the new token
|
||||
client.apiToken = token
|
||||
|
||||
_pairingResult.value = "Paired successfully! Serial: $serial"
|
||||
|
||||
// Fetch statuses now that we're paired
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue