update + Fix

This commit is contained in:
Johnny committed 2026-07-03 07:19:17 +02:00
1 parent d168c69c66
commit 202ee1cc58
13 files changed
+475 -4

No files matched your search

@@ -15,10 +15,15 @@ data class AppSettings(
val cutoffIntervalMinutes: Int = 15,
val baselineMobileBytesAtReset: Long = 0L,
val whitelistedPackages: Set<String> = emptySet(),
val showSystemApps: Boolean = false
val showSystemApps: Boolean = false,
val passwordHash: String? = null,
val passwordSalt: String? = null
) {
val planStartDate: LocalDate get() = LocalDate.ofEpochDay(planStartDateEpochDay)
/** Un mot de passe de verrouillage protège la désactivation du contrôle et l'accès aux réglages. */
val hasPassword: Boolean get() = !passwordHash.isNullOrEmpty()
/** Prochaine date de réinitialisation automatique (anniversaire mensuel du début de forfait). */
fun nextAutoResetDate(): LocalDate {
var next = planStartDate.plusMonths(1)
@@ -0,0 +1,23 @@
package com.jttools.datacontrol.data
import java.security.MessageDigest
import java.security.SecureRandom
/** Hachage salé (SHA-256) du mot de passe de verrouillage — jamais stocké en clair. */
object PasswordHasher {
private const val SALT_BYTES = 16
fun newSalt(): String {
val bytes = ByteArray(SALT_BYTES)
SecureRandom().nextBytes(bytes)
return bytes.toHex()
}
fun hash(password: String, saltHex: String): String {
val digest = MessageDigest.getInstance("SHA-256")
digest.update(saltHex.toByteArray(Charsets.UTF_8))
return digest.digest(password.toByteArray(Charsets.UTF_8)).toHex()
}
private fun ByteArray.toHex(): String = joinToString("") { "%02x".format(it) }
}
@@ -6,10 +6,12 @@ import androidx.datastore.preferences.core.doublePreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import java.time.LocalDate
import javax.inject.Inject
@@ -32,6 +34,8 @@ class SettingsRepository @Inject constructor(
val BASELINE_MOBILE_BYTES = longPreferencesKey("baseline_mobile_bytes")
val WHITELISTED_PACKAGES = stringSetPreferencesKey("whitelisted_packages")
val SHOW_SYSTEM_APPS = booleanPreferencesKey("show_system_apps")
val PASSWORD_HASH = stringPreferencesKey("password_hash")
val PASSWORD_SALT = stringPreferencesKey("password_salt")
}
val settingsFlow: Flow<AppSettings> = context.dataStore.data.map { prefs ->
@@ -46,7 +50,9 @@ class SettingsRepository @Inject constructor(
cutoffIntervalMinutes = prefs[Keys.CUTOFF_INTERVAL_MINUTES] ?: defaults.cutoffIntervalMinutes,
baselineMobileBytesAtReset = prefs[Keys.BASELINE_MOBILE_BYTES] ?: defaults.baselineMobileBytesAtReset,
whitelistedPackages = prefs[Keys.WHITELISTED_PACKAGES] ?: defaults.whitelistedPackages,
showSystemApps = prefs[Keys.SHOW_SYSTEM_APPS] ?: defaults.showSystemApps
showSystemApps = prefs[Keys.SHOW_SYSTEM_APPS] ?: defaults.showSystemApps,
passwordHash = prefs[Keys.PASSWORD_HASH],
passwordSalt = prefs[Keys.PASSWORD_SALT]
)
}
@@ -95,4 +101,29 @@ class SettingsRepository @Inject constructor(
suspend fun rebaseline(currentMobileBytes: Long) {
context.dataStore.edit { it[Keys.BASELINE_MOBILE_BYTES] = currentMobileBytes }
}
/** Définit (ou supprime, si [password] est vide/null) le mot de passe de verrouillage. */
suspend fun setPassword(password: String?) {
if (password.isNullOrBlank()) {
context.dataStore.edit {
it.remove(Keys.PASSWORD_HASH)
it.remove(Keys.PASSWORD_SALT)
}
} else {
val salt = PasswordHasher.newSalt()
val hash = PasswordHasher.hash(password, salt)
context.dataStore.edit { prefs ->
prefs[Keys.PASSWORD_HASH] = hash
prefs[Keys.PASSWORD_SALT] = salt
}
}
}
/** Vrai si aucun mot de passe n'est défini, ou si [password] correspond au mot de passe stocké. */
suspend fun verifyPassword(password: String): Boolean {
val prefs = context.dataStore.data.first()
val hash = prefs[Keys.PASSWORD_HASH] ?: return true
val salt = prefs[Keys.PASSWORD_SALT] ?: return true
return PasswordHasher.hash(password, salt) == hash
}
}