mirror of
https://github.com/AChep/keyguard-app.git
synced 2025-02-02 23:27:35 +01:00
feat(Watchtower): Allow disabling 2FA/Passkeys/Pwned passwords/Breaches checks
This commit is contained in:
parent
71389bdf81
commit
24f1f6aa24
@ -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
|
||||
) {
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.artemchep.keyguard.common.exception
|
||||
|
||||
import com.artemchep.keyguard.common.model.NoAnalytics
|
||||
|
||||
class GravatarDisabledException : RuntimeException(), NoAnalytics
|
@ -0,0 +1,5 @@
|
||||
package com.artemchep.keyguard.common.exception.watchtower
|
||||
|
||||
import com.artemchep.keyguard.common.model.NoAnalytics
|
||||
|
||||
class PasskeysDisabledException : RuntimeException(), NoAnalytics
|
@ -0,0 +1,5 @@
|
||||
package com.artemchep.keyguard.common.exception.watchtower
|
||||
|
||||
import com.artemchep.keyguard.common.model.NoAnalytics
|
||||
|
||||
class PasswordPwnedDisabledException : RuntimeException(), NoAnalytics
|
@ -0,0 +1,5 @@
|
||||
package com.artemchep.keyguard.common.exception.watchtower
|
||||
|
||||
import com.artemchep.keyguard.common.model.NoAnalytics
|
||||
|
||||
class ServicePwnedDisabledException : RuntimeException(), NoAnalytics
|
@ -0,0 +1,5 @@
|
||||
package com.artemchep.keyguard.common.exception.watchtower
|
||||
|
||||
import com.artemchep.keyguard.common.model.NoAnalytics
|
||||
|
||||
class TwoFaDisabledException : RuntimeException(), NoAnalytics
|
@ -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()
|
||||
|
@ -53,6 +53,8 @@ interface SettingsReadRepository {
|
||||
|
||||
fun getCheckTwoFA(): Flow<Boolean>
|
||||
|
||||
fun getCheckPasskeys(): Flow<Boolean>
|
||||
|
||||
fun getWriteAccess(): Flow<Boolean>
|
||||
|
||||
fun getDebugPremium(): Flow<Boolean>
|
||||
|
@ -89,6 +89,10 @@ interface SettingsReadWriteRepository : SettingsReadRepository {
|
||||
checkTwoFA: Boolean,
|
||||
): IO<Unit>
|
||||
|
||||
fun setCheckPasskeys(
|
||||
checkPasskeys: Boolean,
|
||||
): IO<Unit>
|
||||
|
||||
fun setWriteAccess(
|
||||
writeAccess: Boolean,
|
||||
): IO<Unit>
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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>
|
@ -0,0 +1,5 @@
|
||||
package com.artemchep.keyguard.common.usecase
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface GetCheckPasskeys : () -> Flow<Boolean>
|
@ -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>>
|
@ -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>>
|
@ -0,0 +1,5 @@
|
||||
package com.artemchep.keyguard.common.usecase
|
||||
|
||||
import com.artemchep.keyguard.common.io.IO
|
||||
|
||||
interface PutCheckPasskeys : (Boolean) -> IO<Unit>
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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(),
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
<!--
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user