feat(Watchtower): Allow disabling 2FA/Passkeys/Pwned passwords/Breaches checks

This commit is contained in:
Artem Chepurnoy 2024-04-12 20:49:08 +03:00
parent 71389bdf81
commit 24f1f6aa24
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
39 changed files with 619 additions and 130 deletions

View File

@ -1,5 +1,6 @@
package com.artemchep.keyguard.platform
import com.artemchep.keyguard.common.model.NoAnalytics
import com.artemchep.keyguard.platform.util.isRelease
import com.google.firebase.crashlytics.isEnabled
import com.google.firebase.crashlytics.isEnabledFlow
@ -12,6 +13,7 @@ import java.net.UnknownHostException
actual fun recordException(e: Throwable) {
if (
e is NoAnalytics ||
e is UnknownHostException ||
e is SocketException
) {

View File

@ -0,0 +1,5 @@
package com.artemchep.keyguard.common.exception
import com.artemchep.keyguard.common.model.NoAnalytics
class GravatarDisabledException : RuntimeException(), NoAnalytics

View File

@ -0,0 +1,5 @@
package com.artemchep.keyguard.common.exception.watchtower
import com.artemchep.keyguard.common.model.NoAnalytics
class PasskeysDisabledException : RuntimeException(), NoAnalytics

View File

@ -0,0 +1,5 @@
package com.artemchep.keyguard.common.exception.watchtower
import com.artemchep.keyguard.common.model.NoAnalytics
class PasswordPwnedDisabledException : RuntimeException(), NoAnalytics

View File

@ -0,0 +1,5 @@
package com.artemchep.keyguard.common.exception.watchtower
import com.artemchep.keyguard.common.model.NoAnalytics
class ServicePwnedDisabledException : RuntimeException(), NoAnalytics

View File

@ -0,0 +1,5 @@
package com.artemchep.keyguard.common.exception.watchtower
import com.artemchep.keyguard.common.model.NoAnalytics
class TwoFaDisabledException : RuntimeException(), NoAnalytics

View File

@ -24,6 +24,9 @@ import com.artemchep.keyguard.common.usecase.CipherExpiringCheck
import com.artemchep.keyguard.common.usecase.CipherIncompleteCheck
import com.artemchep.keyguard.common.usecase.CipherUnsecureUrlCheck
import com.artemchep.keyguard.common.usecase.CipherUrlDuplicateCheck
import com.artemchep.keyguard.common.usecase.GetBreaches
import com.artemchep.keyguard.common.usecase.GetPasskeys
import com.artemchep.keyguard.common.usecase.GetTwoFa
import com.artemchep.keyguard.core.store.bitwarden.exists
import com.artemchep.keyguard.feature.crashlytics.crashlyticsTap
import com.artemchep.keyguard.feature.home.vault.component.obscurePassword
@ -768,9 +771,9 @@ sealed interface DFilter {
ciphers: List<DSecret>,
): Set<String> = ioEffect {
val check: CipherBreachCheck = directDI.instance()
val repo: BreachesRepository = directDI.instance()
val getBreaches: GetBreaches = directDI.instance()
val breaches = repo.get()
val breaches = getBreaches()
.handleError {
HibpBreachGroup(emptyList())
}
@ -1020,8 +1023,8 @@ sealed interface DFilter {
directDI: DirectDI,
ciphers: List<DSecret>,
) = kotlin.run {
val tfaService = directDI.instance<TwoFaService>()
val tfa = tfaService.get()
val tfaService = directDI.instance<GetTwoFa>()
val tfa = tfaService()
.crashlyticsTap()
.attempt()
.bind()
@ -1125,8 +1128,8 @@ sealed interface DFilter {
directDI: DirectDI,
ciphers: List<DSecret>,
) = kotlin.run {
val passkeyService = directDI.instance<PassKeyService>()
val tfa = passkeyService.get()
val getPasskeys = directDI.instance<GetPasskeys>()
val tfa = getPasskeys()
.crashlyticsTap()
.attempt()
.bind()

View File

@ -53,6 +53,8 @@ interface SettingsReadRepository {
fun getCheckTwoFA(): Flow<Boolean>
fun getCheckPasskeys(): Flow<Boolean>
fun getWriteAccess(): Flow<Boolean>
fun getDebugPremium(): Flow<Boolean>

View File

@ -89,6 +89,10 @@ interface SettingsReadWriteRepository : SettingsReadRepository {
checkTwoFA: Boolean,
): IO<Unit>
fun setCheckPasskeys(
checkPasskeys: Boolean,
): IO<Unit>
fun setWriteAccess(
writeAccess: Boolean,
): IO<Unit>

View File

@ -52,9 +52,10 @@ class SettingsRepositoryImpl(
private const val KEY_CLIPBOARD_UPDATE_DURATION = "clipboard_update_duration"
private const val KEY_CONCEAL_FIELDS = "conceal_fields"
private const val KEY_ALLOW_SCREENSHOTS = "allow_screenshots"
private const val KEY_CHECK_PWNED_PASSWORDS = "check_pwned_passwords"
private const val KEY_CHECK_PWNED_SERVICES = "check_pwned_services"
private const val KEY_CHECK_TWO_FA = "check_two_fa"
private const val KEY_CHECK_PWNED_PASSWORDS = "watchtower_check_pwned_passwords"
private const val KEY_CHECK_PWNED_SERVICES = "watchtower_check_pwned_services"
private const val KEY_CHECK_TWO_FA = "watchtower_check_two_fa"
private const val KEY_CHECK_PASSKEYS = "watchtower_check_passkeys"
private const val KEY_WRITE_ACCESS = "exp.22.write_access"
private const val KEY_DEBUG_PREMIUM = "debug_premium"
private const val KEY_DEBUG_SCREEN_DELAY = "debug_screen_delay"
@ -124,13 +125,16 @@ class SettingsRepositoryImpl(
store.getBoolean(KEY_ALLOW_SCREENSHOTS, false)
private val checkPwnedPasswordsPref =
store.getBoolean(KEY_CHECK_PWNED_PASSWORDS, false)
store.getBoolean(KEY_CHECK_PWNED_PASSWORDS, true)
private val checkPwnedServicesPref =
store.getBoolean(KEY_CHECK_PWNED_SERVICES, false)
store.getBoolean(KEY_CHECK_PWNED_SERVICES, true)
private val checkTwoFAPref =
store.getBoolean(KEY_CHECK_TWO_FA, false)
store.getBoolean(KEY_CHECK_TWO_FA, true)
private val checkPasskeysPref =
store.getBoolean(KEY_CHECK_PASSKEYS, true)
private val writeAccessPref =
store.getBoolean(KEY_WRITE_ACCESS, true)
@ -328,6 +332,11 @@ class SettingsRepositoryImpl(
override fun getCheckTwoFA() = checkTwoFAPref
override fun setCheckPasskeys(checkPasskeys: Boolean) = checkPasskeysPref
.setAndCommit(checkPasskeys)
override fun getCheckPasskeys() = checkPasskeysPref
override fun setDebugPremium(premium: Boolean) = debugPremiumPref
.setAndCommit(premium)

View File

@ -0,0 +1,6 @@
package com.artemchep.keyguard.common.usecase
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.provider.bitwarden.entity.HibpBreachGroup
interface GetBreaches : () -> IO<HibpBreachGroup>

View File

@ -0,0 +1,5 @@
package com.artemchep.keyguard.common.usecase
import kotlinx.coroutines.flow.Flow
interface GetCheckPasskeys : () -> Flow<Boolean>

View File

@ -0,0 +1,6 @@
package com.artemchep.keyguard.common.usecase
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.service.passkey.PassKeyServiceInfo
interface GetPasskeys : () -> IO<List<PassKeyServiceInfo>>

View File

@ -0,0 +1,6 @@
package com.artemchep.keyguard.common.usecase
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.service.twofa.TwoFaServiceInfo
interface GetTwoFa : () -> IO<List<TwoFaServiceInfo>>

View File

@ -0,0 +1,5 @@
package com.artemchep.keyguard.common.usecase
import com.artemchep.keyguard.common.io.IO
interface PutCheckPasskeys : (Boolean) -> IO<Unit>

View File

@ -0,0 +1,34 @@
package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.exception.watchtower.ServicePwnedDisabledException
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.flatMap
import com.artemchep.keyguard.common.io.ioRaise
import com.artemchep.keyguard.common.io.toIO
import com.artemchep.keyguard.common.service.hibp.breaches.all.BreachesRepository
import com.artemchep.keyguard.common.usecase.GetBreaches
import com.artemchep.keyguard.common.usecase.GetCheckPwnedServices
import com.artemchep.keyguard.provider.bitwarden.entity.HibpBreachGroup
import org.kodein.di.DirectDI
import org.kodein.di.instance
class GetBreachesImpl(
private val breachesRepository: BreachesRepository,
private val getCheckPwnedServices: GetCheckPwnedServices,
) : GetBreaches {
constructor(directDI: DirectDI) : this(
breachesRepository = directDI.instance(),
getCheckPwnedServices = directDI.instance(),
)
override fun invoke(): IO<HibpBreachGroup> = getCheckPwnedServices()
.toIO()
.flatMap { enabled ->
if (!enabled) {
val e = ServicePwnedDisabledException()
return@flatMap ioRaise(e)
}
breachesRepository.get()
}
}

View File

@ -0,0 +1,20 @@
package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.service.settings.SettingsReadRepository
import com.artemchep.keyguard.common.usecase.GetCheckPasskeys
import kotlinx.coroutines.flow.distinctUntilChanged
import org.kodein.di.DirectDI
import org.kodein.di.instance
class GetCheckPasskeysImpl(
settingsReadRepository: SettingsReadRepository,
) : GetCheckPasskeys {
private val sharedFlow = settingsReadRepository.getCheckPasskeys()
.distinctUntilChanged()
constructor(directDI: DirectDI) : this(
settingsReadRepository = directDI.instance(),
)
override fun invoke() = sharedFlow
}

View File

@ -1,5 +1,6 @@
package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.exception.GravatarDisabledException
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.ioEffect
import com.artemchep.keyguard.common.service.crypto.CryptoGenerator
@ -19,8 +20,6 @@ class GetGravatarUrlImpl(
) : GetGravatarUrl {
private val emailPlusAddressingRegex = "\\+.+(?=@)".toRegex()
class GravatarDisabledException : RuntimeException()
constructor(directDI: DirectDI) : this(
cryptoGenerator = directDI.instance(),
getGravatar = directDI.instance(),

View File

@ -0,0 +1,34 @@
package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.exception.watchtower.TwoFaDisabledException
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.flatMap
import com.artemchep.keyguard.common.io.ioRaise
import com.artemchep.keyguard.common.io.toIO
import com.artemchep.keyguard.common.service.passkey.PassKeyService
import com.artemchep.keyguard.common.service.passkey.PassKeyServiceInfo
import com.artemchep.keyguard.common.usecase.GetCheckPasskeys
import com.artemchep.keyguard.common.usecase.GetPasskeys
import org.kodein.di.DirectDI
import org.kodein.di.instance
class GetPasskeysImpl(
private val passKeyService: PassKeyService,
private val getCheckPasskeys: GetCheckPasskeys,
) : GetPasskeys {
constructor(directDI: DirectDI) : this(
passKeyService = directDI.instance(),
getCheckPasskeys = directDI.instance(),
)
override fun invoke(): IO<List<PassKeyServiceInfo>> = getCheckPasskeys()
.toIO()
.flatMap { enabled ->
if (!enabled) {
val e = TwoFaDisabledException()
return@flatMap ioRaise(e)
}
passKeyService.get()
}
}

View File

@ -0,0 +1,34 @@
package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.exception.watchtower.TwoFaDisabledException
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.flatMap
import com.artemchep.keyguard.common.io.ioRaise
import com.artemchep.keyguard.common.io.toIO
import com.artemchep.keyguard.common.service.twofa.TwoFaService
import com.artemchep.keyguard.common.service.twofa.TwoFaServiceInfo
import com.artemchep.keyguard.common.usecase.GetCheckTwoFA
import com.artemchep.keyguard.common.usecase.GetTwoFa
import org.kodein.di.DirectDI
import org.kodein.di.instance
class GetTwoFaImpl(
private val twoFaService: TwoFaService,
private val getCheckTwoFA: GetCheckTwoFA,
) : GetTwoFa {
constructor(directDI: DirectDI) : this(
twoFaService = directDI.instance(),
getCheckTwoFA = directDI.instance(),
)
override fun invoke(): IO<List<TwoFaServiceInfo>> = getCheckTwoFA()
.toIO()
.flatMap { enabled ->
if (!enabled) {
val e = TwoFaDisabledException()
return@flatMap ioRaise(e)
}
twoFaService.get()
}
}

View File

@ -0,0 +1,18 @@
package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.service.settings.SettingsReadWriteRepository
import com.artemchep.keyguard.common.usecase.PutCheckPasskeys
import org.kodein.di.DirectDI
import org.kodein.di.instance
class PutCheckPasskeysImpl(
private val settingsReadWriteRepository: SettingsReadWriteRepository,
) : PutCheckPasskeys {
constructor(directDI: DirectDI) : this(
settingsReadWriteRepository = directDI.instance(),
)
override fun invoke(checkPasskeys: Boolean): IO<Unit> = settingsReadWriteRepository
.setCheckPasskeys(checkPasskeys)
}

View File

@ -73,6 +73,7 @@ import com.artemchep.keyguard.common.usecase.GetAccountHasError
import com.artemchep.keyguard.common.usecase.GetAccountStatus
import com.artemchep.keyguard.common.usecase.GetAccounts
import com.artemchep.keyguard.common.usecase.GetAccountsHasError
import com.artemchep.keyguard.common.usecase.GetBreaches
import com.artemchep.keyguard.common.usecase.GetCanAddAccount
import com.artemchep.keyguard.common.usecase.GetCipherOpenedCount
import com.artemchep.keyguard.common.usecase.GetCipherOpenedHistory
@ -132,6 +133,7 @@ import com.artemchep.keyguard.common.usecase.impl.AddUrlOverrideImpl
import com.artemchep.keyguard.common.usecase.impl.DownloadAttachmentImpl2
import com.artemchep.keyguard.common.usecase.impl.EditWordlistImpl
import com.artemchep.keyguard.common.usecase.impl.GetAccountStatusImpl
import com.artemchep.keyguard.common.usecase.impl.GetBreachesImpl
import com.artemchep.keyguard.common.usecase.impl.GetCanAddAccountImpl
import com.artemchep.keyguard.common.usecase.impl.GetEnvSendUrlImpl
import com.artemchep.keyguard.common.usecase.impl.GetGeneratorHistoryImpl
@ -553,6 +555,11 @@ fun DI.Builder.createSubDi2(
bindSingleton<BreachesRepository> {
BreachesRepositoryImpl(this)
}
bindSingleton<GetBreaches> {
GetBreachesImpl(
directDI = this,
)
}
bindSingleton<AddUriCipher> {
AddUriCipherImpl(this)
}

View File

@ -185,7 +185,7 @@ private val items = listOfNotNull<SettingsItem2>(
text = TextHolder.Res(Res.strings.pref_item_watchtower_text),
icon = Icons.Outlined.Security,
route = WatchtowerSettingsRoute,
).takeIf { !isRelease },
),
SettingsItem(
id = "notifications",
title = TextHolder.Res(Res.strings.pref_item_notifications_title),

View File

@ -35,6 +35,7 @@ import com.artemchep.keyguard.feature.home.settings.component.settingAutofillSav
import com.artemchep.keyguard.feature.home.settings.component.settingAutofillSaveUriProvider
import com.artemchep.keyguard.feature.home.settings.component.settingBiometricsProvider
import com.artemchep.keyguard.feature.home.settings.component.settingBiometricsRequireConfirmationProvider
import com.artemchep.keyguard.feature.home.settings.component.settingCheckPasskeysProvider
import com.artemchep.keyguard.feature.home.settings.component.settingCheckPwnedPasswordsProvider
import com.artemchep.keyguard.feature.home.settings.component.settingCheckPwnedServicesProvider
import com.artemchep.keyguard.feature.home.settings.component.settingCheckTwoFAProvider
@ -179,6 +180,7 @@ object Setting {
const val CHECK_PWNED_PASSWORDS = "check_pwned_passwords"
const val CHECK_PWNED_SERVICES = "check_pwned_services"
const val CHECK_TWO_FA = "check_two_fa"
const val CHECK_PASSKEYS = "check_passkeys"
const val CLEAR_CACHE = "clear_cache"
const val APK = "apk"
const val SUBSCRIPTIONS = "subscriptions"
@ -263,6 +265,7 @@ val hub = mapOf<String, (DirectDI) -> SettingComponent>(
Setting.CHECK_PWNED_PASSWORDS to ::settingCheckPwnedPasswordsProvider,
Setting.CHECK_PWNED_SERVICES to ::settingCheckPwnedServicesProvider,
Setting.CHECK_TWO_FA to ::settingCheckTwoFAProvider,
Setting.CHECK_PASSKEYS to ::settingCheckPasskeysProvider,
Setting.CLEAR_CACHE to ::settingClearCache,
Setting.APK to ::settingApkProvider,
Setting.SUBSCRIPTIONS to ::settingSubscriptionsProvider,

View File

@ -0,0 +1,86 @@
package com.artemchep.keyguard.feature.home.settings.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import arrow.core.partially1
import com.artemchep.keyguard.common.io.launchIn
import com.artemchep.keyguard.common.usecase.GetCheckPasskeys
import com.artemchep.keyguard.common.usecase.PutCheckPasskeys
import com.artemchep.keyguard.common.usecase.WindowCoroutineScope
import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.FlatItem
import com.artemchep.keyguard.ui.poweredby.PoweredByPasskeys
import com.artemchep.keyguard.ui.theme.Dimens
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.map
import org.kodein.di.DirectDI
import org.kodein.di.instance
fun settingCheckPasskeysProvider(
directDI: DirectDI,
) = settingCheckPasskeysProvider(
getCheckPasskeys = directDI.instance(),
putCheckPasskeys = directDI.instance(),
windowCoroutineScope = directDI.instance(),
)
fun settingCheckPasskeysProvider(
getCheckPasskeys: GetCheckPasskeys,
putCheckPasskeys: PutCheckPasskeys,
windowCoroutineScope: WindowCoroutineScope,
): SettingComponent = getCheckPasskeys().map { checkPasskeys ->
val onCheckedChange = { shouldCheckPasskeys: Boolean ->
putCheckPasskeys(shouldCheckPasskeys)
.launchIn(windowCoroutineScope)
Unit
}
SettingIi {
SettingCheckPasskeys(
checked = checkPasskeys,
onCheckedChange = onCheckedChange,
)
}
}
@Composable
private fun SettingCheckPasskeys(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
) {
Column {
FlatItem(
trailing = {
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
Switch(
checked = checked,
enabled = onCheckedChange != null,
onCheckedChange = onCheckedChange,
)
}
},
title = {
Text(
text = stringResource(Res.strings.pref_item_check_inactive_passkeys_title),
)
},
text = {
val text = stringResource(Res.strings.watchtower_item_inactive_passkey_text)
Text(text)
},
onClick = onCheckedChange?.partially1(!checked),
)
PoweredByPasskeys(
modifier = Modifier
.padding(horizontal = Dimens.horizontalPadding),
)
}
}

View File

@ -1,16 +1,24 @@
package com.artemchep.keyguard.feature.home.settings.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import arrow.core.partially1
import com.artemchep.keyguard.common.io.launchIn
import com.artemchep.keyguard.common.usecase.GetCheckPwnedPasswords
import com.artemchep.keyguard.common.usecase.PutCheckPwnedPasswords
import com.artemchep.keyguard.common.usecase.WindowCoroutineScope
import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.FlatItem
import com.artemchep.keyguard.ui.poweredby.PoweredByHaveibeenpwned
import com.artemchep.keyguard.ui.theme.Dimens
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.map
import org.kodein.di.DirectDI
import org.kodein.di.instance
@ -47,25 +55,33 @@ private fun SettingCheckPwnedPasswords(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
) {
FlatItem(
trailing = {
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
Switch(
checked = checked,
enabled = onCheckedChange != null,
onCheckedChange = onCheckedChange,
Column {
FlatItem(
trailing = {
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
Switch(
checked = checked,
enabled = onCheckedChange != null,
onCheckedChange = onCheckedChange,
)
}
},
title = {
Text(
text = stringResource(Res.strings.pref_item_check_pwned_passwords_title),
)
}
},
title = {
Text("Check for vulnerable passwords")
},
text = {
val text = "Check saved passwords for recent security breaches."
Text(text)
},
onClick = onCheckedChange?.partially1(!checked),
)
},
text = {
val text = stringResource(Res.strings.watchtower_item_pwned_passwords_text)
Text(text)
},
onClick = onCheckedChange?.partially1(!checked),
)
PoweredByHaveibeenpwned(
modifier = Modifier
.padding(horizontal = Dimens.horizontalPadding),
)
}
}

View File

@ -1,16 +1,23 @@
package com.artemchep.keyguard.feature.home.settings.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import arrow.core.partially1
import com.artemchep.keyguard.common.io.launchIn
import com.artemchep.keyguard.common.usecase.GetCheckPwnedServices
import com.artemchep.keyguard.common.usecase.PutCheckPwnedServices
import com.artemchep.keyguard.common.usecase.WindowCoroutineScope
import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.FlatItem
import com.artemchep.keyguard.ui.poweredby.PoweredByHaveibeenpwned
import com.artemchep.keyguard.ui.theme.Dimens
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.map
import org.kodein.di.DirectDI
import org.kodein.di.instance
@ -47,25 +54,33 @@ private fun SettingCheckPwnedServices(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
) {
FlatItem(
trailing = {
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
Switch(
checked = checked,
enabled = onCheckedChange != null,
onCheckedChange = onCheckedChange,
Column {
FlatItem(
trailing = {
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
Switch(
checked = checked,
enabled = onCheckedChange != null,
onCheckedChange = onCheckedChange,
)
}
},
title = {
Text(
text = stringResource(Res.strings.pref_item_check_pwned_services_title),
)
}
},
title = {
Text("Check for compromised websites")
},
text = {
val text = "Check saved websites for recent security breaches."
Text(text)
},
onClick = onCheckedChange?.partially1(!checked),
)
},
text = {
val text = stringResource(Res.strings.watchtower_item_vulnerable_accounts_text)
Text(text)
},
onClick = onCheckedChange?.partially1(!checked),
)
PoweredByHaveibeenpwned(
modifier = Modifier
.padding(horizontal = Dimens.horizontalPadding),
)
}
}

View File

@ -1,16 +1,24 @@
package com.artemchep.keyguard.feature.home.settings.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import arrow.core.partially1
import com.artemchep.keyguard.common.io.launchIn
import com.artemchep.keyguard.common.usecase.GetCheckTwoFA
import com.artemchep.keyguard.common.usecase.PutCheckTwoFA
import com.artemchep.keyguard.common.usecase.WindowCoroutineScope
import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.FlatItem
import com.artemchep.keyguard.ui.poweredby.PoweredBy2factorauth
import com.artemchep.keyguard.ui.poweredby.PoweredByHaveibeenpwned
import com.artemchep.keyguard.ui.theme.Dimens
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.map
import org.kodein.di.DirectDI
import org.kodein.di.instance
@ -47,25 +55,33 @@ private fun SettingCheckTwoFA(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
) {
FlatItem(
trailing = {
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
Switch(
checked = checked,
enabled = onCheckedChange != null,
onCheckedChange = onCheckedChange,
Column {
FlatItem(
trailing = {
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
Switch(
checked = checked,
enabled = onCheckedChange != null,
onCheckedChange = onCheckedChange,
)
}
},
title = {
Text(
text = stringResource(Res.strings.pref_item_check_inactive_2fa_title),
)
}
},
title = {
Text("Check for two-factor authentication")
},
text = {
val text = "Check for login items that support two-factor authentication."
Text(text)
},
onClick = onCheckedChange?.partially1(!checked),
)
},
text = {
val text = stringResource(Res.strings.watchtower_item_inactive_2fa_text)
Text(text)
},
onClick = onCheckedChange?.partially1(!checked),
)
PoweredBy2factorauth(
modifier = Modifier
.padding(horizontal = Dimens.horizontalPadding),
)
}
}

View File

@ -1,6 +1,7 @@
package com.artemchep.keyguard.feature.home.settings.watchtower
import androidx.compose.runtime.Composable
import com.artemchep.keyguard.feature.home.settings.Setting
import com.artemchep.keyguard.feature.home.settings.SettingPaneContent
import com.artemchep.keyguard.feature.home.settings.SettingPaneItem
import com.artemchep.keyguard.res.Res
@ -11,9 +12,10 @@ fun WatchtowerSettingsScreen() {
SettingPaneContent(
title = stringResource(Res.strings.settings_watchtower_header_title),
items = listOf(
SettingPaneItem.Item("check_pwned_services"),
SettingPaneItem.Item("check_pwned_passwords"),
SettingPaneItem.Item("check_two_fa"),
SettingPaneItem.Item(Setting.CHECK_PWNED_PASSWORDS),
SettingPaneItem.Item(Setting.CHECK_PWNED_SERVICES),
SettingPaneItem.Item(Setting.CHECK_TWO_FA),
SettingPaneItem.Item(Setting.CHECK_PASSKEYS),
),
)
}

View File

@ -112,8 +112,10 @@ import com.artemchep.keyguard.common.usecase.GetJustDeleteMeByUrl
import com.artemchep.keyguard.common.usecase.GetJustGetMyDataByUrl
import com.artemchep.keyguard.common.usecase.GetMarkdown
import com.artemchep.keyguard.common.usecase.GetOrganizations
import com.artemchep.keyguard.common.usecase.GetPasskeys
import com.artemchep.keyguard.common.usecase.GetPasswordStrength
import com.artemchep.keyguard.common.usecase.GetTotpCode
import com.artemchep.keyguard.common.usecase.GetTwoFa
import com.artemchep.keyguard.common.usecase.GetUrlOverrides
import com.artemchep.keyguard.common.usecase.GetWebsiteIcons
import com.artemchep.keyguard.common.usecase.MoveCipherToFolderById
@ -235,6 +237,8 @@ fun vaultViewScreenState(
getMarkdown = instance(),
getAppIcons = instance(),
getWebsiteIcons = instance(),
getPasskeys = instance(),
getTwoFa = instance(),
getTotpCode = instance(),
getPasswordStrength = instance(),
getUrlOverrides = instance(),
@ -261,8 +265,6 @@ fun vaultViewScreenState(
cipherExpiringCheck = instance(),
cipherIncompleteCheck = instance(),
cipherUrlCheck = instance(),
passKeyService = instance(),
tfaService = instance(),
clipboardService = instance(),
getGravatarUrl = instance(),
dateFormatter = instance(),
@ -312,6 +314,8 @@ fun vaultViewScreenState(
getMarkdown: GetMarkdown,
getAppIcons: GetAppIcons,
getWebsiteIcons: GetWebsiteIcons,
getPasskeys: GetPasskeys,
getTwoFa: GetTwoFa,
getTotpCode: GetTotpCode,
getPasswordStrength: GetPasswordStrength,
getUrlOverrides: GetUrlOverrides,
@ -338,8 +342,6 @@ fun vaultViewScreenState(
cipherExpiringCheck: CipherExpiringCheck,
cipherIncompleteCheck: CipherIncompleteCheck,
cipherUrlCheck: CipherUrlCheck,
passKeyService: PassKeyService,
tfaService: TwoFaService,
clipboardService: ClipboardService,
getGravatarUrl: GetGravatarUrl,
dateFormatter: DateFormatter,
@ -732,8 +734,8 @@ fun vaultViewScreenState(
downloadManager = downloadManager,
downloadAttachment = downloadAttachment,
removeAttachment = removeAttachment,
passKeyService = passKeyService,
tfaService = tfaService,
getPasskeys = getPasskeys,
getTwoFa = getTwoFa,
getTotpCode = getTotpCode,
getPasswordStrength = getPasswordStrength,
passkeyTargetCheck = passkeyTargetCheck,
@ -785,8 +787,8 @@ private fun RememberStateFlowScope.oh(
downloadManager: DownloadManager,
downloadAttachment: DownloadAttachment,
removeAttachment: RemoveAttachment,
passKeyService: PassKeyService,
tfaService: TwoFaService,
getPasskeys: GetPasskeys,
getTwoFa: GetTwoFa,
getTotpCode: GetTotpCode,
getPasswordStrength: GetPasswordStrength,
passkeyTargetCheck: PasskeyTargetCheck,
@ -1114,7 +1116,7 @@ private fun RememberStateFlowScope.oh(
return@run
}
val tfa = tfaService.get()
val tfa = getTwoFa()
.crashlyticsTap()
.attempt()
.bind()
@ -1144,7 +1146,7 @@ private fun RememberStateFlowScope.oh(
cipher.login.fido2Credentials.isEmpty() &&
!cipher.ignores(DWatchtowerAlert.PASSKEY_WEBSITE)
) {
val tfa = passKeyService.get()
val tfa = getPasskeys()
.crashlyticsTap()
.attempt()
.bind()

View File

@ -11,6 +11,7 @@ import arrow.core.partially1
import com.artemchep.keyguard.common.model.Loadable
import com.artemchep.keyguard.common.service.passkey.PassKeyService
import com.artemchep.keyguard.common.service.passkey.PassKeyServiceInfo
import com.artemchep.keyguard.common.usecase.GetPasskeys
import com.artemchep.keyguard.feature.crashlytics.crashlyticsAttempt
import com.artemchep.keyguard.feature.favicon.FaviconImage
import com.artemchep.keyguard.feature.favicon.FaviconUrl
@ -36,13 +37,13 @@ private class PasskeysServiceListUiException(
fun producePasskeysListState(
) = with(localDI().direct) {
producePasskeysListState(
passKeyService = instance(),
getPasskeys = instance(),
)
}
@Composable
fun producePasskeysListState(
passKeyService: PassKeyService,
getPasskeys: GetPasskeys,
): Loadable<PasskeysServiceListState> = produceScreenState(
key = "passkeys_service_list",
initial = Loadable.Loading,
@ -112,7 +113,7 @@ fun producePasskeysListState(
}
}
val itemsFlow = passKeyService.get()
val itemsFlow = getPasskeys()
.asFlow()
.map { apps ->
apps

View File

@ -11,6 +11,7 @@ import arrow.core.partially1
import com.artemchep.keyguard.common.model.Loadable
import com.artemchep.keyguard.common.service.twofa.TwoFaService
import com.artemchep.keyguard.common.service.twofa.TwoFaServiceInfo
import com.artemchep.keyguard.common.usecase.GetTwoFa
import com.artemchep.keyguard.feature.crashlytics.crashlyticsAttempt
import com.artemchep.keyguard.feature.favicon.FaviconImage
import com.artemchep.keyguard.feature.favicon.FaviconUrl
@ -36,13 +37,13 @@ private class TwoFaServiceListUiException(
fun produceTwoFaServiceListState(
) = with(localDI().direct) {
produceTwoFaServiceListState(
twoFaService = instance(),
getTwoFa = instance(),
)
}
@Composable
fun produceTwoFaServiceListState(
twoFaService: TwoFaService,
getTwoFa: GetTwoFa,
): Loadable<TwoFaServiceListState> = produceScreenState(
key = "tfa_service_list",
initial = Loadable.Loading,
@ -111,7 +112,7 @@ fun produceTwoFaServiceListState(
}
}
val itemsFlow = twoFaService.get()
val itemsFlow = getTwoFa()
.asFlow()
.map { apps ->
apps

View File

@ -11,6 +11,10 @@ import com.artemchep.keyguard.common.model.PasswordStrength
import com.artemchep.keyguard.common.model.formatH2
import com.artemchep.keyguard.common.usecase.CipherDuplicatesCheck
import com.artemchep.keyguard.common.usecase.GetAccounts
import com.artemchep.keyguard.common.usecase.GetCheckPasskeys
import com.artemchep.keyguard.common.usecase.GetCheckPwnedPasswords
import com.artemchep.keyguard.common.usecase.GetCheckPwnedServices
import com.artemchep.keyguard.common.usecase.GetCheckTwoFA
import com.artemchep.keyguard.common.usecase.GetCiphers
import com.artemchep.keyguard.common.usecase.GetCollections
import com.artemchep.keyguard.common.usecase.GetFolders
@ -44,6 +48,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
@ -66,6 +71,10 @@ fun produceWatchtowerState(
getFolders = instance(),
getCollections = instance(),
getOrganizations = instance(),
getCheckPwnedPasswords = instance(),
getCheckPwnedServices = instance(),
getCheckTwoFA = instance(),
getCheckPasskeys = instance(),
cipherDuplicatesCheck = instance(),
)
}
@ -90,6 +99,10 @@ fun produceWatchtowerState(
getFolders: GetFolders,
getCollections: GetCollections,
getOrganizations: GetOrganizations,
getCheckPwnedPasswords: GetCheckPwnedPasswords,
getCheckPwnedServices: GetCheckPwnedServices,
getCheckTwoFA: GetCheckTwoFA,
getCheckPasskeys: GetCheckPasskeys,
cipherDuplicatesCheck: CipherDuplicatesCheck,
): WatchtowerState = produceScreenState(
initial = WatchtowerState(),
@ -248,6 +261,7 @@ fun produceWatchtowerState(
fun <S, T> boose(
source: Flow<FilteredBoo<S>>,
key: String,
enabledFlow: Flow<Boolean>,
counterFlow: (FilteredBoo<S>) -> Flow<Int>,
onCreate: (FilteredBoo<S>?, Int) -> T,
): StateFlow<Loadable<T?>> {
@ -265,18 +279,31 @@ fun produceWatchtowerState(
Loadable.Loading
}
}
return source
.flatMapLatest { holder ->
counterFlow(holder)
.onEach {
val revision = holder.filterConfig?.id
if (revision == null || revision == 0) {
cachedCounterSink.value = it
}
}
.map { count ->
val state = onCreate(holder, count)
Loadable.Ok(state)
return enabledFlow
.distinctUntilChanged()
.flatMapLatest { enabled ->
if (!enabled) {
// Reset the cache when the feature is
// not enabled.
cachedCounterSink.value = -1
val result = Loadable.Ok(null)
return@flatMapLatest flowOf(result)
}
source
.flatMapLatest { holder ->
counterFlow(holder)
.onEach {
val revision = holder.filterConfig?.id
if (revision == null || revision == 0) {
cachedCounterSink.value = it
}
}
.map { count ->
val state = onCreate(holder, count)
Loadable.Ok(state)
}
}
}
.crashlyticsMap(
@ -296,11 +323,13 @@ fun produceWatchtowerState(
fun <S, T> booseBlock(
source: Flow<FilteredBoo<S>>,
key: String,
enabledFlow: Flow<Boolean> = flowOf(true),
counterBlock: suspend (FilteredBoo<S>) -> Int,
onCreate: (FilteredBoo<S>?, Int) -> T,
) = boose(
source = source,
key = key,
enabledFlow = enabledFlow,
counterFlow = { holder ->
flow {
val count = counterBlock(holder)
@ -414,6 +443,7 @@ fun produceWatchtowerState(
val passwordPwnedFlow = booseBlock(
source = filteredCiphersFlow,
key = DFilter.ByPasswordPwned.key,
enabledFlow = getCheckPwnedPasswords(),
counterBlock = { holder ->
val count = DFilter.ByPasswordPwned.count(directDI, holder.list)
count
@ -543,6 +573,7 @@ fun produceWatchtowerState(
val inactiveTwoFactorAuthFlow = booseBlock(
source = filteredCiphersFlow,
key = DFilter.ByTfaWebsites.key,
enabledFlow = getCheckTwoFA(),
counterBlock = { holder ->
val count = DFilter.ByTfaWebsites.count(directDI, holder.list)
count
@ -586,6 +617,7 @@ fun produceWatchtowerState(
val inactivePasskeyFlow = booseBlock(
source = filteredCiphersFlow,
key = DFilter.ByPasskeyWebsites.key,
enabledFlow = getCheckPasskeys(),
counterBlock = { holder ->
val count = DFilter.ByPasskeyWebsites.count(directDI, holder.list)
count
@ -673,6 +705,7 @@ fun produceWatchtowerState(
val websitePwnedFlow = booseBlock(
source = filteredCiphersFlow,
key = DFilter.ByWebsitePwned.key,
enabledFlow = getCheckPwnedServices(),
counterBlock = { holder ->
val count = DFilter.ByWebsitePwned.count(directDI, holder.list)
count
@ -965,28 +998,38 @@ fun produceWatchtowerState(
emptyItems = emptyItemsFlow,
strength = passwordStrengthFlow,
)
val actions = buildContextItems {
section {
this += TwoFaServicesRoute.actionOrNull(
translator = this@produceScreenState,
navigate = ::navigate,
)
this += PasskeysServicesRoute.actionOrNull(
translator = this@produceScreenState,
navigate = ::navigate,
)
this += JustGetMyDataServicesRoute.actionOrNull(
translator = this@produceScreenState,
navigate = ::navigate,
)
this += JustDeleteMeServicesRoute.actionOrNull(
translator = this@produceScreenState,
navigate = ::navigate,
)
val actionsFlow = combine(
getCheckTwoFA(),
getCheckPasskeys(),
) { checkTwoFa, checkPasskeys ->
val actions = buildContextItems {
section {
if (checkTwoFa) {
this += TwoFaServicesRoute.actionOrNull(
translator = this@produceScreenState,
navigate = ::navigate,
)
}
if (checkPasskeys) {
this += PasskeysServicesRoute.actionOrNull(
translator = this@produceScreenState,
navigate = ::navigate,
)
}
this += JustGetMyDataServicesRoute.actionOrNull(
translator = this@produceScreenState,
navigate = ::navigate,
)
this += JustDeleteMeServicesRoute.actionOrNull(
translator = this@produceScreenState,
navigate = ::navigate,
)
}
}
actions
}
filterFlow
.map { filterState ->
.combine(actionsFlow) { filterState, actions ->
WatchtowerState(
revision = filterState.rev,
content = Loadable.Ok(content),

View File

@ -7,6 +7,7 @@ import com.artemchep.keyguard.common.io.bind
import com.artemchep.keyguard.common.model.Loadable
import com.artemchep.keyguard.common.service.hibp.breaches.all.BreachesRepository
import com.artemchep.keyguard.common.usecase.DateFormatter
import com.artemchep.keyguard.common.usecase.GetBreaches
import com.artemchep.keyguard.feature.navigation.state.navigatePopSelf
import com.artemchep.keyguard.feature.navigation.state.produceScreenState
import kotlinx.collections.immutable.toImmutableList
@ -25,7 +26,7 @@ fun produceWebsiteLeakState(
) = with(localDI().direct) {
produceWebsiteLeakState(
args = args,
breachesRepository = instance(),
getBreaches = instance(),
dateFormatter = instance(),
)
}
@ -33,14 +34,14 @@ fun produceWebsiteLeakState(
@Composable
fun produceWebsiteLeakState(
args: WebsiteLeakRoute.Args,
breachesRepository: BreachesRepository,
getBreaches: GetBreaches,
dateFormatter: DateFormatter,
): Loadable<WebsiteLeakState> = produceScreenState(
key = "website_leak",
initial = Loadable.Loading,
args = arrayOf(),
) {
val breaches2 = breachesRepository.get()
val breaches2 = getBreaches()
.attempt()
.bind()
val breach3 = breaches2

View File

@ -1,10 +1,15 @@
package com.artemchep.keyguard.provider.bitwarden.usecase
import com.artemchep.keyguard.common.exception.watchtower.PasswordPwnedDisabledException
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.flatMap
import com.artemchep.keyguard.common.io.ioRaise
import com.artemchep.keyguard.common.io.map
import com.artemchep.keyguard.common.io.toIO
import com.artemchep.keyguard.common.model.CheckPasswordLeakRequest
import com.artemchep.keyguard.common.service.hibp.passwords.PasswordPwnageRepository
import com.artemchep.keyguard.common.usecase.CheckPasswordLeak
import com.artemchep.keyguard.common.usecase.GetCheckPwnedPasswords
import org.kodein.di.DirectDI
import org.kodein.di.instance
@ -13,14 +18,31 @@ import org.kodein.di.instance
*/
class CheckPasswordLeakImpl(
private val passwordPwnageRepository: PasswordPwnageRepository,
private val getCheckPwnedPasswords: GetCheckPwnedPasswords,
) : CheckPasswordLeak {
constructor(directDI: DirectDI) : this(
passwordPwnageRepository = directDI.instance(),
getCheckPwnedPasswords = directDI.instance(),
)
override fun invoke(
request: CheckPasswordLeakRequest,
): IO<Int> = passwordPwnageRepository
): IO<Int> = getCheckPwnedPasswords()
.toIO()
.flatMap { canCheckPwnedPassword ->
if (!canCheckPwnedPassword) {
val e = PasswordPwnedDisabledException()
return@flatMap ioRaise(e)
}
performCheckPwnedPassword(
request = request,
)
}
private fun performCheckPwnedPassword(
request: CheckPasswordLeakRequest,
) = passwordPwnageRepository
.checkOne(
password = request.password,
cache = request.cache,

View File

@ -1,10 +1,15 @@
package com.artemchep.keyguard.provider.bitwarden.usecase
import com.artemchep.keyguard.common.exception.watchtower.PasswordPwnedDisabledException
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.flatMap
import com.artemchep.keyguard.common.io.ioRaise
import com.artemchep.keyguard.common.io.toIO
import com.artemchep.keyguard.common.model.CheckPasswordSetLeakRequest
import com.artemchep.keyguard.common.model.PasswordPwnage
import com.artemchep.keyguard.common.service.hibp.passwords.PasswordPwnageRepository
import com.artemchep.keyguard.common.usecase.CheckPasswordSetLeak
import com.artemchep.keyguard.common.usecase.GetCheckPwnedPasswords
import org.kodein.di.DirectDI
import org.kodein.di.instance
@ -13,14 +18,31 @@ import org.kodein.di.instance
*/
class CheckPasswordSetLeakImpl(
private val passwordPwnageRepository: PasswordPwnageRepository,
private val getCheckPwnedPasswords: GetCheckPwnedPasswords,
) : CheckPasswordSetLeak {
constructor(directDI: DirectDI) : this(
passwordPwnageRepository = directDI.instance(),
getCheckPwnedPasswords = directDI.instance(),
)
override fun invoke(
request: CheckPasswordSetLeakRequest,
): IO<Map<String, PasswordPwnage?>> = passwordPwnageRepository
): IO<Map<String, PasswordPwnage?>> = getCheckPwnedPasswords()
.toIO()
.flatMap { canCheckPwnedPassword ->
if (!canCheckPwnedPassword) {
val e = PasswordPwnedDisabledException()
return@flatMap ioRaise(e)
}
performCheckPwnedPasswords(
request = request,
)
}
private fun performCheckPwnedPasswords(
request: CheckPasswordSetLeakRequest,
) = passwordPwnageRepository
.checkMany(
passwords = request.passwords,
cache = request.cache,

View File

@ -978,6 +978,10 @@
<string name="pref_item_color_scheme_amoled_dark_title">Use contrast black theme</string>
<string name="pref_item_open_links_in_external_browser_title">Open links in external browser</string>
<string name="pref_item_close_to_tray_title">Close to system tray</string>
<string name="pref_item_check_pwned_passwords_title">Check for pwned passwords</string>
<string name="pref_item_check_pwned_services_title">Check for data breaches</string>
<string name="pref_item_check_inactive_2fa_title">Check for inactive two-factor authentication</string>
<string name="pref_item_check_inactive_passkeys_title">Check for inactive passkeys</string>
<string name="pref_item_conceal_fields_title">Conceal fields</string>
<string name="pref_item_conceal_fields_text">Conceal sensitive info, such as passwords and credit card numbers</string>
<!--

View File

@ -1,9 +1,15 @@
package com.artemchep.keyguard.platform
import com.artemchep.keyguard.common.model.NoAnalytics
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
actual fun recordException(e: Throwable) {
if (
e is NoAnalytics
) {
return
}
e.printStackTrace()
}

View File

@ -103,8 +103,10 @@ import com.artemchep.keyguard.common.usecase.GetAutofillSaveUri
import com.artemchep.keyguard.common.usecase.GetBiometricRequireConfirmation
import com.artemchep.keyguard.common.usecase.GetBiometricTimeout
import com.artemchep.keyguard.common.usecase.GetBiometricTimeoutVariants
import com.artemchep.keyguard.common.usecase.GetBreaches
import com.artemchep.keyguard.common.usecase.GetCachePremium
import com.artemchep.keyguard.common.usecase.GetCanWrite
import com.artemchep.keyguard.common.usecase.GetCheckPasskeys
import com.artemchep.keyguard.common.usecase.GetCheckPwnedPasswords
import com.artemchep.keyguard.common.usecase.GetCheckPwnedServices
import com.artemchep.keyguard.common.usecase.GetCheckTwoFA
@ -131,6 +133,7 @@ import com.artemchep.keyguard.common.usecase.GetNavAnimation
import com.artemchep.keyguard.common.usecase.GetNavAnimationVariants
import com.artemchep.keyguard.common.usecase.GetNavLabel
import com.artemchep.keyguard.common.usecase.GetOnboardingLastVisitInstant
import com.artemchep.keyguard.common.usecase.GetPasskeys
import com.artemchep.keyguard.common.usecase.GetPassphrase
import com.artemchep.keyguard.common.usecase.GetPassword
import com.artemchep.keyguard.common.usecase.GetPasswordStrength
@ -141,6 +144,7 @@ import com.artemchep.keyguard.common.usecase.GetTheme
import com.artemchep.keyguard.common.usecase.GetThemeUseAmoledDark
import com.artemchep.keyguard.common.usecase.GetThemeVariants
import com.artemchep.keyguard.common.usecase.GetTotpCode
import com.artemchep.keyguard.common.usecase.GetTwoFa
import com.artemchep.keyguard.common.usecase.GetUseExternalBrowser
import com.artemchep.keyguard.common.usecase.GetVaultLockAfterReboot
import com.artemchep.keyguard.common.usecase.GetVaultLockAfterScreenOff
@ -167,6 +171,7 @@ import com.artemchep.keyguard.common.usecase.PutAutofillSaveUri
import com.artemchep.keyguard.common.usecase.PutBiometricRequireConfirmation
import com.artemchep.keyguard.common.usecase.PutBiometricTimeout
import com.artemchep.keyguard.common.usecase.PutCachePremium
import com.artemchep.keyguard.common.usecase.PutCheckPasskeys
import com.artemchep.keyguard.common.usecase.PutCheckPwnedPasswords
import com.artemchep.keyguard.common.usecase.PutCheckPwnedServices
import com.artemchep.keyguard.common.usecase.PutCheckTwoFA
@ -229,8 +234,10 @@ import com.artemchep.keyguard.common.usecase.impl.GetAutofillSaveUriImpl
import com.artemchep.keyguard.common.usecase.impl.GetBiometricRequireConfirmationImpl
import com.artemchep.keyguard.common.usecase.impl.GetBiometricTimeoutImpl
import com.artemchep.keyguard.common.usecase.impl.GetBiometricTimeoutVariantsImpl
import com.artemchep.keyguard.common.usecase.impl.GetBreachesImpl
import com.artemchep.keyguard.common.usecase.impl.GetCachePremiumImpl
import com.artemchep.keyguard.common.usecase.impl.GetCanWriteImpl
import com.artemchep.keyguard.common.usecase.impl.GetCheckPasskeysImpl
import com.artemchep.keyguard.common.usecase.impl.GetCheckPwnedPasswordsImpl
import com.artemchep.keyguard.common.usecase.impl.GetCheckPwnedServicesImpl
import com.artemchep.keyguard.common.usecase.impl.GetCheckTwoFAImpl
@ -257,6 +264,7 @@ import com.artemchep.keyguard.common.usecase.impl.GetNavAnimationImpl
import com.artemchep.keyguard.common.usecase.impl.GetNavAnimationVariantsImpl
import com.artemchep.keyguard.common.usecase.impl.GetNavLabelImpl
import com.artemchep.keyguard.common.usecase.impl.GetOnboardingLastVisitInstantImpl
import com.artemchep.keyguard.common.usecase.impl.GetPasskeysImpl
import com.artemchep.keyguard.common.usecase.impl.GetPasswordImpl
import com.artemchep.keyguard.common.usecase.impl.GetProductsImpl
import com.artemchep.keyguard.common.usecase.impl.GetScreenStateImpl
@ -265,6 +273,7 @@ import com.artemchep.keyguard.common.usecase.impl.GetThemeImpl
import com.artemchep.keyguard.common.usecase.impl.GetThemeUseAmoledDarkImpl
import com.artemchep.keyguard.common.usecase.impl.GetThemeVariantsImpl
import com.artemchep.keyguard.common.usecase.impl.GetTotpCodeImpl
import com.artemchep.keyguard.common.usecase.impl.GetTwoFaImpl
import com.artemchep.keyguard.common.usecase.impl.GetUseExternalBrowserImpl
import com.artemchep.keyguard.common.usecase.impl.GetVaultLockAfterRebootImpl
import com.artemchep.keyguard.common.usecase.impl.GetVaultLockAfterScreenOffImpl
@ -290,6 +299,7 @@ import com.artemchep.keyguard.common.usecase.impl.PutAutofillSaveUriImpl
import com.artemchep.keyguard.common.usecase.impl.PutBiometricRequireConfirmationImpl
import com.artemchep.keyguard.common.usecase.impl.PutBiometricTimeoutImpl
import com.artemchep.keyguard.common.usecase.impl.PutCachePremiumImpl
import com.artemchep.keyguard.common.usecase.impl.PutCheckPasskeysImpl
import com.artemchep.keyguard.common.usecase.impl.PutCheckPwnedPasswordsImpl
import com.artemchep.keyguard.common.usecase.impl.PutCheckPwnedServicesImpl
import com.artemchep.keyguard.common.usecase.impl.PutCheckTwoFAImpl
@ -440,6 +450,16 @@ fun globalModuleJvm() = DI.Module(
directDI = this,
)
}
bindSingleton<GetTwoFa> {
GetTwoFaImpl(
directDI = this,
)
}
bindSingleton<GetPasskeys> {
GetPasskeysImpl(
directDI = this,
)
}
bindSingleton<GetPassword> {
GetPasswordImpl(
directDI = this,
@ -540,6 +560,11 @@ fun globalModuleJvm() = DI.Module(
directDI = this,
)
}
bindSingleton<GetCheckPasskeys> {
GetCheckPasskeysImpl(
directDI = this,
)
}
bindSingleton<PutCheckPwnedPasswords> {
PutCheckPwnedPasswordsImpl(
directDI = this,
@ -555,6 +580,11 @@ fun globalModuleJvm() = DI.Module(
directDI = this,
)
}
bindSingleton<PutCheckPasskeys> {
PutCheckPasskeysImpl(
directDI = this,
)
}
bindSingleton<PutKeepScreenOn> {
PutKeepScreenOnImpl(
directDI = this,