mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-1419: Username generation options persistence (#586)
This commit is contained in:
parent
1f0a1bba6f
commit
5e2e23edec
9 changed files with 580 additions and 19 deletions
|
@ -1,6 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.tools.generator.datasource.disk
|
package com.x8bit.bitwarden.data.tools.generator.datasource.disk
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary access point for disk information related to generation.
|
* Primary access point for disk information related to generation.
|
||||||
|
@ -16,4 +17,14 @@ interface GeneratorDiskSource {
|
||||||
* Stores a user's passcode generation options using a [userId].
|
* Stores a user's passcode generation options using a [userId].
|
||||||
*/
|
*/
|
||||||
fun storePasscodeGenerationOptions(userId: String, options: PasscodeGenerationOptions?)
|
fun storePasscodeGenerationOptions(userId: String, options: PasscodeGenerationOptions?)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a user's username generation options using a [userId].
|
||||||
|
*/
|
||||||
|
fun getUsernameGenerationOptions(userId: String): UsernameGenerationOptions?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a user's username generation options using a [userId].
|
||||||
|
*/
|
||||||
|
fun storeUsernameGenerationOptions(userId: String, options: UsernameGenerationOptions?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,12 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.disk
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
private const val PASSWORD_GENERATION_OPTIONS_KEY = "passwordGenerationOptions"
|
private const val PASSWORD_GENERATION_OPTIONS_KEY = "passwordGenerationOptions"
|
||||||
|
private const val USERNAME_GENERATION_OPTIONS_KEY = "usernameGenerationOptions"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary implementation of [GeneratorDiskSource].
|
* Primary implementation of [GeneratorDiskSource].
|
||||||
|
@ -35,4 +37,20 @@ class GeneratorDiskSourceImpl(
|
||||||
|
|
||||||
private fun getPasswordGenerationOptionsKey(userId: String): String =
|
private fun getPasswordGenerationOptionsKey(userId: String): String =
|
||||||
"${BASE_KEY}_${PASSWORD_GENERATION_OPTIONS_KEY}_$userId"
|
"${BASE_KEY}_${PASSWORD_GENERATION_OPTIONS_KEY}_$userId"
|
||||||
|
|
||||||
|
override fun getUsernameGenerationOptions(userId: String): UsernameGenerationOptions? {
|
||||||
|
val key = getUsernameGenerationOptionsKey(userId)
|
||||||
|
return getString(key)?.let { json.decodeFromString(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun storeUsernameGenerationOptions(
|
||||||
|
userId: String,
|
||||||
|
options: UsernameGenerationOptions?,
|
||||||
|
) {
|
||||||
|
val key = getUsernameGenerationOptionsKey(userId)
|
||||||
|
putString(key, options?.let { json.encodeToString(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUsernameGenerationOptionsKey(userId: String): String =
|
||||||
|
"${BASE_KEY}_${USERNAME_GENERATION_OPTIONS_KEY}_$userId"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@file:Suppress("TooManyFunctions")
|
||||||
|
|
||||||
package com.x8bit.bitwarden.data.tools.generator.repository
|
package com.x8bit.bitwarden.data.tools.generator.repository
|
||||||
|
|
||||||
import com.bitwarden.core.PassphraseGeneratorRequest
|
import com.bitwarden.core.PassphraseGeneratorRequest
|
||||||
|
@ -12,6 +14,7 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswo
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,6 +82,16 @@ interface GeneratorRepository {
|
||||||
*/
|
*/
|
||||||
fun savePasscodeGenerationOptions(options: PasscodeGenerationOptions)
|
fun savePasscodeGenerationOptions(options: PasscodeGenerationOptions)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the [UsernameGenerationOptions] for the current user.
|
||||||
|
*/
|
||||||
|
fun getUsernameGenerationOptions(): UsernameGenerationOptions?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the [UsernameGenerationOptions] for the current user.
|
||||||
|
*/
|
||||||
|
fun saveUsernameGenerationOptions(options: UsernameGenerationOptions)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store a password history item for the current user.
|
* Store a password history item for the current user.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -22,6 +22,7 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswo
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -193,6 +194,16 @@ class GeneratorRepositoryImpl(
|
||||||
userId?.let { generatorDiskSource.storePasscodeGenerationOptions(it, options) }
|
userId?.let { generatorDiskSource.storePasscodeGenerationOptions(it, options) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getUsernameGenerationOptions(): UsernameGenerationOptions? {
|
||||||
|
val userId = authDiskSource.userState?.activeUserId
|
||||||
|
return userId?.let { generatorDiskSource.getUsernameGenerationOptions(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveUsernameGenerationOptions(options: UsernameGenerationOptions) {
|
||||||
|
val userId = authDiskSource.userState?.activeUserId
|
||||||
|
userId?.let { generatorDiskSource.storeUsernameGenerationOptions(it, options) }
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun storePasswordHistory(passwordHistoryView: PasswordHistoryView) {
|
override suspend fun storePasswordHistory(passwordHistoryView: PasswordHistoryView) {
|
||||||
val userId = authDiskSource.userState?.activeUserId ?: return
|
val userId = authDiskSource.userState?.activeUserId ?: return
|
||||||
val encryptedPasswordHistory = vaultSdkSource
|
val encryptedPasswordHistory = vaultSdkSource
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
package com.x8bit.bitwarden.data.tools.generator.repository.model
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data class representing the configuration options for generating usernames.
|
||||||
|
*
|
||||||
|
* @property type The type of username to be generated, as defined in UsernameType.
|
||||||
|
* @property serviceType The type of email forwarding service to be used,
|
||||||
|
* as defined in ForwardedEmailServiceType.
|
||||||
|
* @property capitalizeRandomWordUsername Indicates whether to capitalize the username.
|
||||||
|
* @property includeNumberRandomWordUsername Indicates whether to include a number in the username.
|
||||||
|
* @property plusAddressedEmail The email address to be used for plus-addressing.
|
||||||
|
* @property catchAllEmailDomain The domain name to be used for catch-all email addresses.
|
||||||
|
* @property firefoxRelayApiAccessToken The API access token for Firefox Relay.
|
||||||
|
* @property simpleLoginApiKey The API key for SimpleLogin.
|
||||||
|
* @property duckDuckGoApiKey The API key for DuckDuckGo.
|
||||||
|
* @property fastMailApiKey The API key for FastMail.
|
||||||
|
* @property anonAddyApiAccessToken The API access token for AnonAddy.
|
||||||
|
* @property anonAddyDomainName The domain name associated with AnonAddy.
|
||||||
|
* @property forwardEmailApiAccessToken The API access token for Forward Email.
|
||||||
|
* @property forwardEmailDomainName The domain name associated with Forward Email.
|
||||||
|
* @property emailWebsite The website associated with the email service.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class UsernameGenerationOptions(
|
||||||
|
@SerialName("type")
|
||||||
|
val type: UsernameType,
|
||||||
|
|
||||||
|
@SerialName("serviceType")
|
||||||
|
val serviceType: ForwardedEmailServiceType? = null,
|
||||||
|
|
||||||
|
@SerialName("capitalizeRandomWordUsername")
|
||||||
|
val capitalizeRandomWordUsername: Boolean? = null,
|
||||||
|
|
||||||
|
@SerialName("includeNumberRandomWordUsername")
|
||||||
|
val includeNumberRandomWordUsername: Boolean? = null,
|
||||||
|
|
||||||
|
@SerialName("plusAddressedEmail")
|
||||||
|
val plusAddressedEmail: String? = null,
|
||||||
|
|
||||||
|
@SerialName("catchAllEmailDomain")
|
||||||
|
val catchAllEmailDomain: String? = null,
|
||||||
|
|
||||||
|
@SerialName("firefoxRelayApiAccessToken")
|
||||||
|
val firefoxRelayApiAccessToken: String? = null,
|
||||||
|
|
||||||
|
@SerialName("simpleLoginApiKey")
|
||||||
|
val simpleLoginApiKey: String? = null,
|
||||||
|
|
||||||
|
@SerialName("duckDuckGoApiKey")
|
||||||
|
val duckDuckGoApiKey: String? = null,
|
||||||
|
|
||||||
|
@SerialName("fastMailApiKey")
|
||||||
|
val fastMailApiKey: String? = null,
|
||||||
|
|
||||||
|
@SerialName("anonAddyApiAccessToken")
|
||||||
|
val anonAddyApiAccessToken: String? = null,
|
||||||
|
|
||||||
|
@SerialName("anonAddyDomainName")
|
||||||
|
val anonAddyDomainName: String? = null,
|
||||||
|
|
||||||
|
@SerialName("forwardEmailApiAccessToken")
|
||||||
|
val forwardEmailApiAccessToken: String? = null,
|
||||||
|
|
||||||
|
@SerialName("forwardEmailDomainName")
|
||||||
|
val forwardEmailDomainName: String? = null,
|
||||||
|
|
||||||
|
@SerialName("emailWebsite")
|
||||||
|
val emailWebsite: String? = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents different Username Types.
|
||||||
|
*/
|
||||||
|
@Serializable(with = UsernameTypeSerializer::class)
|
||||||
|
enum class UsernameType {
|
||||||
|
@SerialName("0")
|
||||||
|
PLUS_ADDRESSED_EMAIL,
|
||||||
|
|
||||||
|
@SerialName("1")
|
||||||
|
CATCH_ALL_EMAIL,
|
||||||
|
|
||||||
|
@SerialName("2")
|
||||||
|
FORWARDED_EMAIL_ALIAS,
|
||||||
|
|
||||||
|
@SerialName("3")
|
||||||
|
RANDOM_WORD,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents different Service Types within the ForwardedEmailAlias Username Type.
|
||||||
|
*/
|
||||||
|
@Serializable(with = ForwardedEmailServiceTypeSerializer::class)
|
||||||
|
enum class ForwardedEmailServiceType {
|
||||||
|
@SerialName("-1")
|
||||||
|
NONE,
|
||||||
|
|
||||||
|
@SerialName("0")
|
||||||
|
ANON_ADDY,
|
||||||
|
|
||||||
|
@SerialName("1")
|
||||||
|
FIREFOX_RELAY,
|
||||||
|
|
||||||
|
@SerialName("2")
|
||||||
|
SIMPLE_LOGIN,
|
||||||
|
|
||||||
|
@SerialName("3")
|
||||||
|
DUCK_DUCK_GO,
|
||||||
|
|
||||||
|
@SerialName("4")
|
||||||
|
FASTMAIL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
private class UsernameTypeSerializer :
|
||||||
|
BaseEnumeratedIntSerializer<UsernameGenerationOptions.UsernameType>(
|
||||||
|
UsernameGenerationOptions.UsernameType.values(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
private class ForwardedEmailServiceTypeSerializer :
|
||||||
|
BaseEnumeratedIntSerializer<UsernameGenerationOptions.ForwardedEmailServiceType>(
|
||||||
|
UsernameGenerationOptions.ForwardedEmailServiceType.values(),
|
||||||
|
)
|
|
@ -20,9 +20,11 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswo
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode
|
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase
|
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password
|
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password
|
||||||
|
@ -140,7 +142,7 @@ class GeneratorViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is GeneratorAction.Internal.UpdateGeneratedForwardedServiceUsernameResult -> {
|
is GeneratorAction.Internal.UpdateGeneratedForwardedServiceUsernameResult -> {
|
||||||
handleUpdateForwadedServiceGeneratedUsernameResult(action)
|
handleUpdateForwardedServiceGeneratedUsernameResult(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
is GeneratorAction.MainType.Username.UsernameTypeOptionSelect -> {
|
is GeneratorAction.MainType.Username.UsernameTypeOptionSelect -> {
|
||||||
|
@ -233,21 +235,48 @@ class GeneratorViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadUsernameOptions(selectedType: Username) {
|
private fun loadUsernameOptions(selectedType: Username) {
|
||||||
val updatedSelectedType = when (selectedType.selectedType) {
|
val options = generatorRepository.getUsernameGenerationOptions()
|
||||||
is PlusAddressedEmail -> Username(
|
val updatedSelectedType = when (val type = selectedType.selectedType) {
|
||||||
selectedType = PlusAddressedEmail(
|
is PlusAddressedEmail -> {
|
||||||
// For convenience the default is an empty email value. We can supply the
|
val emailToUse = options
|
||||||
// dynamic value here before updating the state.
|
?.plusAddressedEmail
|
||||||
email = state.currentEmailAddress,
|
?.orNullIfBlank()
|
||||||
|
?: state.currentEmailAddress
|
||||||
|
|
||||||
|
Username(selectedType = PlusAddressedEmail(email = emailToUse))
|
||||||
|
}
|
||||||
|
|
||||||
|
is CatchAllEmail -> {
|
||||||
|
val catchAllEmail = CatchAllEmail(
|
||||||
|
domainName = options?.catchAllEmailDomain ?: type.domainName,
|
||||||
|
)
|
||||||
|
Username(selectedType = catchAllEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
is RandomWord -> {
|
||||||
|
val randomWord = RandomWord(
|
||||||
|
capitalize = options?.capitalizeRandomWordUsername ?: type.capitalize,
|
||||||
|
includeNumber = options?.includeNumberRandomWordUsername ?: type.includeNumber,
|
||||||
|
)
|
||||||
|
Username(selectedType = randomWord)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ForwardedEmailAlias -> {
|
||||||
|
val mappedServiceType = options
|
||||||
|
?.serviceType
|
||||||
|
?.toServiceType(options)
|
||||||
|
?: type.selectedServiceType
|
||||||
|
|
||||||
|
Username(
|
||||||
|
selectedType = ForwardedEmailAlias(
|
||||||
|
selectedServiceType = mappedServiceType,
|
||||||
|
obfuscatedText = "",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
else -> selectedType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update { it.copy(selectedType = updatedSelectedType) }
|
||||||
it.copy(selectedType = updatedSelectedType)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun savePasswordOptionsToDisk(password: Password) {
|
private fun savePasswordOptionsToDisk(password: Password) {
|
||||||
|
@ -278,6 +307,82 @@ class GeneratorViewModel @Inject constructor(
|
||||||
generatorRepository.savePasscodeGenerationOptions(newOptions)
|
generatorRepository.savePasscodeGenerationOptions(newOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun savePlusAddressedEmailOptionsToDisk(plusAddressedEmail: PlusAddressedEmail) {
|
||||||
|
val options = generatorRepository.getUsernameGenerationOptions()
|
||||||
|
?: generateUsernameDefaultOptions()
|
||||||
|
val newOptions = options.copy(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.PLUS_ADDRESSED_EMAIL,
|
||||||
|
plusAddressedEmail = plusAddressedEmail.email,
|
||||||
|
)
|
||||||
|
|
||||||
|
generatorRepository.saveUsernameGenerationOptions(newOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveCatchAllEmailOptionsToDisk(catchAllEmail: CatchAllEmail) {
|
||||||
|
val options = generatorRepository
|
||||||
|
.getUsernameGenerationOptions() ?: generateUsernameDefaultOptions()
|
||||||
|
val newOptions = options.copy(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.CATCH_ALL_EMAIL,
|
||||||
|
catchAllEmailDomain = catchAllEmail.domainName,
|
||||||
|
)
|
||||||
|
generatorRepository.saveUsernameGenerationOptions(newOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveRandomWordOptionsToDisk(randomWord: RandomWord) {
|
||||||
|
val options = generatorRepository
|
||||||
|
.getUsernameGenerationOptions() ?: generateUsernameDefaultOptions()
|
||||||
|
val newOptions = options.copy(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.RANDOM_WORD,
|
||||||
|
capitalizeRandomWordUsername = randomWord.capitalize,
|
||||||
|
includeNumberRandomWordUsername = randomWord.includeNumber,
|
||||||
|
)
|
||||||
|
generatorRepository.saveUsernameGenerationOptions(newOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveForwardedEmailAliasServiceTypeToDisk(forwardedEmailAlias: ForwardedEmailAlias) {
|
||||||
|
val options =
|
||||||
|
generatorRepository.getUsernameGenerationOptions() ?: generateUsernameDefaultOptions()
|
||||||
|
val newOptions = when (forwardedEmailAlias.selectedServiceType) {
|
||||||
|
is AddyIo -> options.copy(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.FORWARDED_EMAIL_ALIAS,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.ANON_ADDY,
|
||||||
|
anonAddyApiAccessToken = forwardedEmailAlias.selectedServiceType.apiAccessToken,
|
||||||
|
anonAddyDomainName = forwardedEmailAlias.selectedServiceType.domainName,
|
||||||
|
)
|
||||||
|
|
||||||
|
is DuckDuckGo -> options.copy(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.FORWARDED_EMAIL_ALIAS,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.DUCK_DUCK_GO,
|
||||||
|
duckDuckGoApiKey = forwardedEmailAlias.selectedServiceType.apiKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
is FastMail -> options.copy(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.FORWARDED_EMAIL_ALIAS,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.FASTMAIL,
|
||||||
|
fastMailApiKey = forwardedEmailAlias.selectedServiceType.apiKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
is FirefoxRelay -> options.copy(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.FORWARDED_EMAIL_ALIAS,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.FIREFOX_RELAY,
|
||||||
|
firefoxRelayApiAccessToken = forwardedEmailAlias.selectedServiceType.apiAccessToken,
|
||||||
|
)
|
||||||
|
|
||||||
|
is SimpleLogin -> options.copy(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.FORWARDED_EMAIL_ALIAS,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.SIMPLE_LOGIN,
|
||||||
|
simpleLoginApiKey = forwardedEmailAlias.selectedServiceType.apiKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> options.copy(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.FORWARDED_EMAIL_ALIAS,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.NONE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
generatorRepository.saveUsernameGenerationOptions(newOptions)
|
||||||
|
}
|
||||||
|
|
||||||
private fun generatePasscodeDefaultOptions(): PasscodeGenerationOptions {
|
private fun generatePasscodeDefaultOptions(): PasscodeGenerationOptions {
|
||||||
val defaultPassword = Password()
|
val defaultPassword = Password()
|
||||||
val defaultPassphrase = Passphrase()
|
val defaultPassphrase = Passphrase()
|
||||||
|
@ -298,6 +403,26 @@ class GeneratorViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun generateUsernameDefaultOptions(): UsernameGenerationOptions {
|
||||||
|
return UsernameGenerationOptions(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.PLUS_ADDRESSED_EMAIL,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.NONE,
|
||||||
|
capitalizeRandomWordUsername = false,
|
||||||
|
includeNumberRandomWordUsername = false,
|
||||||
|
plusAddressedEmail = "",
|
||||||
|
catchAllEmailDomain = "",
|
||||||
|
firefoxRelayApiAccessToken = "",
|
||||||
|
simpleLoginApiKey = "",
|
||||||
|
duckDuckGoApiKey = "",
|
||||||
|
fastMailApiKey = "",
|
||||||
|
anonAddyApiAccessToken = "",
|
||||||
|
anonAddyDomainName = "",
|
||||||
|
forwardEmailApiAccessToken = "",
|
||||||
|
forwardEmailDomainName = "",
|
||||||
|
emailWebsite = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun generatePassword(password: Password) {
|
private suspend fun generatePassword(password: Password) {
|
||||||
val request = PasswordGeneratorRequest(
|
val request = PasswordGeneratorRequest(
|
||||||
lowercase = password.useLowercase,
|
lowercase = password.useLowercase,
|
||||||
|
@ -423,7 +548,7 @@ class GeneratorViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUpdateForwadedServiceGeneratedUsernameResult(
|
private fun handleUpdateForwardedServiceGeneratedUsernameResult(
|
||||||
action: GeneratorAction.Internal.UpdateGeneratedForwardedServiceUsernameResult,
|
action: GeneratorAction.Internal.UpdateGeneratedForwardedServiceUsernameResult,
|
||||||
) {
|
) {
|
||||||
when (val result = action.result) {
|
when (val result = action.result) {
|
||||||
|
@ -702,25 +827,48 @@ class GeneratorViewModel @Inject constructor(
|
||||||
.ForwardedEmailAlias
|
.ForwardedEmailAlias
|
||||||
.ServiceTypeOptionSelect,
|
.ServiceTypeOptionSelect,
|
||||||
) {
|
) {
|
||||||
|
val options = generatorRepository.getUsernameGenerationOptions()
|
||||||
|
?: generateUsernameDefaultOptions()
|
||||||
when (action.serviceTypeOption) {
|
when (action.serviceTypeOption) {
|
||||||
ForwardedEmailAlias.ServiceTypeOption.ADDY_IO -> updateForwardedEmailAliasType {
|
ForwardedEmailAlias.ServiceTypeOption.ADDY_IO -> updateForwardedEmailAliasType {
|
||||||
ForwardedEmailAlias(selectedServiceType = AddyIo())
|
ForwardedEmailAlias(
|
||||||
|
selectedServiceType = AddyIo(
|
||||||
|
apiAccessToken = options.anonAddyApiAccessToken.orEmpty(),
|
||||||
|
domainName = options.anonAddyDomainName.orEmpty(),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ForwardedEmailAlias.ServiceTypeOption.DUCK_DUCK_GO -> updateForwardedEmailAliasType {
|
ForwardedEmailAlias.ServiceTypeOption.DUCK_DUCK_GO -> updateForwardedEmailAliasType {
|
||||||
ForwardedEmailAlias(selectedServiceType = DuckDuckGo())
|
ForwardedEmailAlias(
|
||||||
|
selectedServiceType = DuckDuckGo(
|
||||||
|
apiKey = options.duckDuckGoApiKey.orEmpty(),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ForwardedEmailAlias.ServiceTypeOption.FAST_MAIL -> updateForwardedEmailAliasType {
|
ForwardedEmailAlias.ServiceTypeOption.FAST_MAIL -> updateForwardedEmailAliasType {
|
||||||
ForwardedEmailAlias(selectedServiceType = FastMail())
|
ForwardedEmailAlias(
|
||||||
|
selectedServiceType = FastMail(
|
||||||
|
apiKey = options.fastMailApiKey.orEmpty(),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ForwardedEmailAlias.ServiceTypeOption.FIREFOX_RELAY -> updateForwardedEmailAliasType {
|
ForwardedEmailAlias.ServiceTypeOption.FIREFOX_RELAY -> updateForwardedEmailAliasType {
|
||||||
ForwardedEmailAlias(selectedServiceType = FirefoxRelay())
|
ForwardedEmailAlias(
|
||||||
|
selectedServiceType = FirefoxRelay(
|
||||||
|
apiAccessToken = options.firefoxRelayApiAccessToken.orEmpty(),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ForwardedEmailAlias.ServiceTypeOption.SIMPLE_LOGIN -> updateForwardedEmailAliasType {
|
ForwardedEmailAlias.ServiceTypeOption.SIMPLE_LOGIN -> updateForwardedEmailAliasType {
|
||||||
ForwardedEmailAlias(selectedServiceType = SimpleLogin())
|
ForwardedEmailAlias(
|
||||||
|
selectedServiceType = SimpleLogin(
|
||||||
|
apiKey = options.simpleLoginApiKey.orEmpty(),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -962,24 +1110,28 @@ class GeneratorViewModel @Inject constructor(
|
||||||
|
|
||||||
is Username -> when (val selectedType = updatedMainType.selectedType) {
|
is Username -> when (val selectedType = updatedMainType.selectedType) {
|
||||||
is ForwardedEmailAlias -> {
|
is ForwardedEmailAlias -> {
|
||||||
|
saveForwardedEmailAliasServiceTypeToDisk(selectedType)
|
||||||
if (isManualRegeneration) {
|
if (isManualRegeneration) {
|
||||||
generateForwardedEmailAlias(selectedType)
|
generateForwardedEmailAlias(selectedType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is CatchAllEmail -> {
|
is CatchAllEmail -> {
|
||||||
|
saveCatchAllEmailOptionsToDisk(selectedType)
|
||||||
if (isManualRegeneration) {
|
if (isManualRegeneration) {
|
||||||
generateCatchAllEmail(selectedType)
|
generateCatchAllEmail(selectedType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is PlusAddressedEmail -> {
|
is PlusAddressedEmail -> {
|
||||||
|
savePlusAddressedEmailOptionsToDisk(selectedType)
|
||||||
if (isManualRegeneration) {
|
if (isManualRegeneration) {
|
||||||
generatePlusAddressedEmail(selectedType)
|
generatePlusAddressedEmail(selectedType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is RandomWord -> {
|
is RandomWord -> {
|
||||||
|
saveRandomWordOptionsToDisk(selectedType)
|
||||||
if (isManualRegeneration) {
|
if (isManualRegeneration) {
|
||||||
generateRandomWordUsername(selectedType)
|
generateRandomWordUsername(selectedType)
|
||||||
}
|
}
|
||||||
|
@ -1024,6 +1176,7 @@ class GeneratorViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
sendAction(GeneratorAction.Internal.UpdateGeneratedRandomWordUsernameResult(result))
|
sendAction(GeneratorAction.Internal.UpdateGeneratedRandomWordUsernameResult(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun updateGeneratorMainTypePasscode(
|
private inline fun updateGeneratorMainTypePasscode(
|
||||||
crossinline block: (Passcode) -> Passcode,
|
crossinline block: (Passcode) -> Passcode,
|
||||||
) {
|
) {
|
||||||
|
@ -2050,3 +2203,34 @@ private fun Password.enforceAtLeastOneToggleOn(): Password =
|
||||||
} else {
|
} else {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun UsernameGenerationOptions.ForwardedEmailServiceType?.toServiceType(
|
||||||
|
options: UsernameGenerationOptions,
|
||||||
|
): ForwardedEmailAlias.ServiceType? {
|
||||||
|
return when (this) {
|
||||||
|
UsernameGenerationOptions.ForwardedEmailServiceType.FIREFOX_RELAY -> {
|
||||||
|
FirefoxRelay(apiAccessToken = options.firefoxRelayApiAccessToken.orEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
UsernameGenerationOptions.ForwardedEmailServiceType.SIMPLE_LOGIN -> {
|
||||||
|
SimpleLogin(apiKey = options.simpleLoginApiKey.orEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
UsernameGenerationOptions.ForwardedEmailServiceType.DUCK_DUCK_GO -> {
|
||||||
|
DuckDuckGo(apiKey = options.duckDuckGoApiKey.orEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
UsernameGenerationOptions.ForwardedEmailServiceType.FASTMAIL -> {
|
||||||
|
FastMail(apiKey = options.fastMailApiKey.orEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
UsernameGenerationOptions.ForwardedEmailServiceType.ANON_ADDY -> {
|
||||||
|
AddyIo(
|
||||||
|
apiAccessToken = options.anonAddyApiAccessToken.orEmpty(),
|
||||||
|
domainName = options.anonAddyDomainName.orEmpty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.disk
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
|
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
@ -89,4 +90,72 @@ class GeneratorDiskSourceTest {
|
||||||
assertNotNull(storedValue)
|
assertNotNull(storedValue)
|
||||||
assertEquals(json.encodeToString(options), storedValue)
|
assertEquals(json.encodeToString(options), storedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getUsernameGenerationOptions should return correct options when available`() {
|
||||||
|
val userId = "user123"
|
||||||
|
val options = UsernameGenerationOptions(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.RANDOM_WORD,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.NONE,
|
||||||
|
capitalizeRandomWordUsername = true,
|
||||||
|
includeNumberRandomWordUsername = false,
|
||||||
|
plusAddressedEmail = "example+plus@gmail.com",
|
||||||
|
catchAllEmailDomain = "example.com",
|
||||||
|
firefoxRelayApiAccessToken = "access_token_firefox_relay",
|
||||||
|
simpleLoginApiKey = "api_key_simple_login",
|
||||||
|
duckDuckGoApiKey = "api_key_duck_duck_go",
|
||||||
|
fastMailApiKey = "api_key_fast_mail",
|
||||||
|
anonAddyApiAccessToken = "access_token_anon_addy",
|
||||||
|
anonAddyDomainName = "anonaddy.com",
|
||||||
|
forwardEmailApiAccessToken = "access_token_forward_email",
|
||||||
|
forwardEmailDomainName = "forwardemail.net",
|
||||||
|
emailWebsite = "email.example.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
val key = "bwPreferencesStorage_usernameGenerationOptions_$userId"
|
||||||
|
fakeSharedPreferences.edit().putString(key, json.encodeToString(options)).apply()
|
||||||
|
|
||||||
|
val result = generatorDiskSource.getUsernameGenerationOptions(userId)
|
||||||
|
|
||||||
|
assertEquals(options, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getUsernameGenerationOptions should return null when options are not available`() {
|
||||||
|
val userId = "user123"
|
||||||
|
|
||||||
|
val result = generatorDiskSource.getUsernameGenerationOptions(userId)
|
||||||
|
|
||||||
|
assertNull(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `storeUsernameGenerationOptions should correctly store options`() {
|
||||||
|
val userId = "user123"
|
||||||
|
val options = UsernameGenerationOptions(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.RANDOM_WORD,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.NONE,
|
||||||
|
capitalizeRandomWordUsername = true,
|
||||||
|
includeNumberRandomWordUsername = false,
|
||||||
|
plusAddressedEmail = "example+plus@gmail.com",
|
||||||
|
catchAllEmailDomain = "example.com",
|
||||||
|
firefoxRelayApiAccessToken = "access_token_firefox_relay",
|
||||||
|
simpleLoginApiKey = "api_key_simple_login",
|
||||||
|
duckDuckGoApiKey = "api_key_duck_duck_go",
|
||||||
|
fastMailApiKey = "api_key_fast_mail",
|
||||||
|
anonAddyApiAccessToken = "access_token_anon_addy",
|
||||||
|
anonAddyDomainName = "anonaddy.com",
|
||||||
|
forwardEmailApiAccessToken = "access_token_forward_email",
|
||||||
|
forwardEmailDomainName = "forwardemail.net",
|
||||||
|
emailWebsite = "email.example.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
val key = "bwPreferencesStorage_usernameGenerationOptions_$userId"
|
||||||
|
|
||||||
|
generatorDiskSource.storeUsernameGenerationOptions(userId, options)
|
||||||
|
|
||||||
|
val storedValue = fakeSharedPreferences.getString(key, null)
|
||||||
|
assertNotNull(storedValue)
|
||||||
|
assertEquals(json.encodeToString(options), storedValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswo
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
|
@ -50,6 +51,7 @@ import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Suppress("LargeClass")
|
||||||
class GeneratorRepositoryTest {
|
class GeneratorRepositoryTest {
|
||||||
|
|
||||||
private val mutableUserStateFlow = MutableStateFlow<UserStateJson?>(null)
|
private val mutableUserStateFlow = MutableStateFlow<UserStateJson?>(null)
|
||||||
|
@ -644,6 +646,119 @@ class GeneratorRepositoryTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getUsernameGenerationOptions should return options when available`() = runTest {
|
||||||
|
val userId = "activeUserId"
|
||||||
|
val expectedOptions = UsernameGenerationOptions(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.RANDOM_WORD,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.NONE,
|
||||||
|
capitalizeRandomWordUsername = true,
|
||||||
|
includeNumberRandomWordUsername = false,
|
||||||
|
plusAddressedEmail = "example+plus@gmail.com",
|
||||||
|
catchAllEmailDomain = "example.com",
|
||||||
|
firefoxRelayApiAccessToken = "access_token_firefox_relay",
|
||||||
|
simpleLoginApiKey = "api_key_simple_login",
|
||||||
|
duckDuckGoApiKey = "api_key_duck_duck_go",
|
||||||
|
fastMailApiKey = "api_key_fast_mail",
|
||||||
|
anonAddyApiAccessToken = "access_token_anon_addy",
|
||||||
|
anonAddyDomainName = "anonaddy.com",
|
||||||
|
forwardEmailApiAccessToken = "access_token_forward_email",
|
||||||
|
forwardEmailDomainName = "forwardemail.net",
|
||||||
|
emailWebsite = "email.example.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
coEvery { authDiskSource.userState } returns USER_STATE
|
||||||
|
coEvery { generatorDiskSource.getUsernameGenerationOptions(userId) } returns expectedOptions
|
||||||
|
|
||||||
|
val result = repository.getUsernameGenerationOptions()
|
||||||
|
|
||||||
|
assertEquals(expectedOptions, result)
|
||||||
|
coVerify { generatorDiskSource.getUsernameGenerationOptions(userId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getUsernameGenerationOptions should return null when there is no active user`() = runTest {
|
||||||
|
coEvery { authDiskSource.userState } returns null
|
||||||
|
|
||||||
|
val result = repository.getUsernameGenerationOptions()
|
||||||
|
|
||||||
|
assertNull(result)
|
||||||
|
coVerify(exactly = 0) { generatorDiskSource.getUsernameGenerationOptions(any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getUsernameGenerationOptions should return null when no data is stored for active user`() =
|
||||||
|
runTest {
|
||||||
|
val userId = "activeUserId"
|
||||||
|
coEvery { authDiskSource.userState } returns USER_STATE
|
||||||
|
coEvery { generatorDiskSource.getUsernameGenerationOptions(userId) } returns null
|
||||||
|
|
||||||
|
val result = repository.getUsernameGenerationOptions()
|
||||||
|
|
||||||
|
assertNull(result)
|
||||||
|
coVerify { generatorDiskSource.getUsernameGenerationOptions(userId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `saveUsernameGenerationOptions should store options correctly`() = runTest {
|
||||||
|
val userId = "activeUserId"
|
||||||
|
val optionsToSave = UsernameGenerationOptions(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.RANDOM_WORD,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.NONE,
|
||||||
|
capitalizeRandomWordUsername = true,
|
||||||
|
includeNumberRandomWordUsername = false,
|
||||||
|
plusAddressedEmail = "example+plus@gmail.com",
|
||||||
|
catchAllEmailDomain = "example.com",
|
||||||
|
firefoxRelayApiAccessToken = "access_token_firefox_relay",
|
||||||
|
simpleLoginApiKey = "api_key_simple_login",
|
||||||
|
duckDuckGoApiKey = "api_key_duck_duck_go",
|
||||||
|
fastMailApiKey = "api_key_fast_mail",
|
||||||
|
anonAddyApiAccessToken = "access_token_anon_addy",
|
||||||
|
anonAddyDomainName = "anonaddy.com",
|
||||||
|
forwardEmailApiAccessToken = "access_token_forward_email",
|
||||||
|
forwardEmailDomainName = "forwardemail.net",
|
||||||
|
emailWebsite = "email.example.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
coEvery { authDiskSource.userState } returns USER_STATE
|
||||||
|
coEvery {
|
||||||
|
generatorDiskSource.storeUsernameGenerationOptions(userId, optionsToSave)
|
||||||
|
} just runs
|
||||||
|
|
||||||
|
repository.saveUsernameGenerationOptions(optionsToSave)
|
||||||
|
|
||||||
|
coVerify { generatorDiskSource.storeUsernameGenerationOptions(userId, optionsToSave) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `saveUsernameGenerationOptions should not store options when there is no active user`() =
|
||||||
|
runTest {
|
||||||
|
val optionsToSave = UsernameGenerationOptions(
|
||||||
|
type = UsernameGenerationOptions.UsernameType.RANDOM_WORD,
|
||||||
|
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.NONE,
|
||||||
|
capitalizeRandomWordUsername = true,
|
||||||
|
includeNumberRandomWordUsername = false,
|
||||||
|
plusAddressedEmail = "example+plus@gmail.com",
|
||||||
|
catchAllEmailDomain = "example.com",
|
||||||
|
firefoxRelayApiAccessToken = "access_token_firefox_relay",
|
||||||
|
simpleLoginApiKey = "api_key_simple_login",
|
||||||
|
duckDuckGoApiKey = "api_key_duck_duck_go",
|
||||||
|
fastMailApiKey = "api_key_fast_mail",
|
||||||
|
anonAddyApiAccessToken = "access_token_anon_addy",
|
||||||
|
anonAddyDomainName = "anonaddy.com",
|
||||||
|
forwardEmailApiAccessToken = "access_token_forward_email",
|
||||||
|
forwardEmailDomainName = "forwardemail.net",
|
||||||
|
emailWebsite = "email.example.com",
|
||||||
|
)
|
||||||
|
coEvery { authDiskSource.userState } returns null
|
||||||
|
|
||||||
|
repository.saveUsernameGenerationOptions(optionsToSave)
|
||||||
|
|
||||||
|
coVerify(exactly = 0) {
|
||||||
|
generatorDiskSource.storeUsernameGenerationOptions(any(), any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val USER_STATE = UserStateJson(
|
private val USER_STATE = UserStateJson(
|
||||||
activeUserId = "activeUserId",
|
activeUserId = "activeUserId",
|
||||||
accounts = mapOf(
|
accounts = mapOf(
|
||||||
|
|
|
@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswo
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPlusAddressedUsernameResult
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedRandomWordUsernameResult
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.UsernameGenerationOptions
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
@ -30,6 +31,8 @@ class FakeGeneratorRepository : GeneratorRepository {
|
||||||
)
|
)
|
||||||
private var passcodeGenerationOptions: PasscodeGenerationOptions? = null
|
private var passcodeGenerationOptions: PasscodeGenerationOptions? = null
|
||||||
|
|
||||||
|
private var usernameGenerationOptions: UsernameGenerationOptions? = null
|
||||||
|
|
||||||
private val mutablePasswordHistoryStateFlow =
|
private val mutablePasswordHistoryStateFlow =
|
||||||
MutableStateFlow<LocalDataState<List<PasswordHistoryView>>>(LocalDataState.Loading)
|
MutableStateFlow<LocalDataState<List<PasswordHistoryView>>>(LocalDataState.Loading)
|
||||||
|
|
||||||
|
@ -101,6 +104,14 @@ class FakeGeneratorRepository : GeneratorRepository {
|
||||||
passcodeGenerationOptions = options
|
passcodeGenerationOptions = options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getUsernameGenerationOptions(): UsernameGenerationOptions? {
|
||||||
|
return usernameGenerationOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveUsernameGenerationOptions(options: UsernameGenerationOptions) {
|
||||||
|
usernameGenerationOptions = options
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun storePasswordHistory(passwordHistoryView: PasswordHistoryView) {
|
override suspend fun storePasswordHistory(passwordHistoryView: PasswordHistoryView) {
|
||||||
val currentList = mutablePasswordHistoryStateFlow.value.data.orEmpty()
|
val currentList = mutablePasswordHistoryStateFlow.value.data.orEmpty()
|
||||||
val updatedList = currentList + passwordHistoryView
|
val updatedList = currentList + passwordHistoryView
|
||||||
|
|
Loading…
Reference in a new issue