Add AuthSdkSource (#118)

This commit is contained in:
Brian Yencho
2023-10-16 13:52:18 -05:00
committed by Álison Fernandes
parent 84d10d7634
commit 69a4eef68f
12 changed files with 591 additions and 16 deletions

View File

@@ -0,0 +1,50 @@
package com.x8bit.bitwarden.data.auth.datasource.sdk
import com.bitwarden.core.Kdf
import com.bitwarden.core.MasterPasswordPolicyOptions
import com.bitwarden.core.RegisterKeyResponse
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
/**
* Source of authentication information and functionality from the Bitwarden SDK.
*/
interface AuthSdkSource {
/**
* Creates a hashed password provided the given [email], [password], and [kdf].
* [kdf].
*/
suspend fun hashPassword(
email: String,
password: String,
kdf: Kdf,
): Result<String>
/**
* Creates a set of encryption key information for registraation pers
*/
suspend fun makeRegisterKeys(
email: String,
password: String,
kdf: Kdf,
): Result<RegisterKeyResponse>
/**
* Checks the password strength for the given [email] and [password] combination, along with
* some [additionalInputs].
*/
suspend fun passwordStrength(
email: String,
password: String,
additionalInputs: List<String> = emptyList(),
): Result<PasswordStrength>
/**
* Checks that the given [password] with the given [passwordStrength] satisfies the given
* [policy]. Returns `true` if so and `false` otherwise.
*/
suspend fun satisfiesPolicy(
password: String,
passwordStrength: PasswordStrength,
policy: MasterPasswordPolicyOptions,
): Result<Boolean>
}

View File

@@ -0,0 +1,69 @@
package com.x8bit.bitwarden.data.auth.datasource.sdk
import com.bitwarden.core.Kdf
import com.bitwarden.core.MasterPasswordPolicyOptions
import com.bitwarden.core.RegisterKeyResponse
import com.bitwarden.sdk.ClientAuth
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toPasswordStrengthOrNull
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toUByte
/**
* Primary implementation of [AuthSdkSource] that serves as a convenience wrapper around a
* [ClientAuth].
*/
class AuthSdkSourceImpl(
private val clientAuth: ClientAuth,
) : AuthSdkSource {
override suspend fun hashPassword(
email: String,
password: String,
kdf: Kdf,
): Result<String> = runCatching {
clientAuth.hashPassword(
email = email,
password = password,
kdfParams = kdf,
)
}
override suspend fun makeRegisterKeys(
email: String,
password: String,
kdf: Kdf,
): Result<RegisterKeyResponse> = runCatching {
clientAuth.makeRegisterKeys(
email = email,
password = password,
kdf = kdf,
)
}
override suspend fun passwordStrength(
email: String,
password: String,
additionalInputs: List<String>,
): Result<PasswordStrength> = runCatching {
@Suppress("UnsafeCallOnNullableType")
clientAuth
.passwordStrength(
password = password,
email = email,
additionalInputs = additionalInputs,
)
.toPasswordStrengthOrNull()!!
}
override suspend fun satisfiesPolicy(
password: String,
passwordStrength: PasswordStrength,
policy: MasterPasswordPolicyOptions,
): Result<Boolean> = runCatching {
clientAuth.satisfiesPolicy(
password = password,
strength = passwordStrength.toUByte(),
policy = policy,
)
}
}

View File

@@ -0,0 +1,24 @@
package com.x8bit.bitwarden.data.auth.datasource.sdk.di
import com.bitwarden.sdk.Client
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSourceImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
* Provides SDK-related dependencies for the auth package.
*/
@Module
@InstallIn(SingletonComponent::class)
object SdkModule {
@Provides
@Singleton
fun provideAuthSdkSource(
client: Client,
): AuthSdkSource = AuthSdkSourceImpl(clientAuth = client.auth())
}

View File

@@ -0,0 +1,33 @@
package com.x8bit.bitwarden.data.auth.datasource.sdk.model
/**
* An estimate of password strength.
*
* Adapted from [zxcvbn](https://github.com/dropbox/zxcvbn#usage).
*/
enum class PasswordStrength {
/**
* Too guessable; very risky.
*/
LEVEL_0,
/**
* Very guessable; limited protection.
*/
LEVEL_1,
/**
* Somewhat guessable; some protection.
*/
LEVEL_2,
/**
* Safely unguessable; moderate protection.
*/
LEVEL_3,
/**
* Very unguessable; strong protection.
*/
LEVEL_4,
}

View File

@@ -0,0 +1,59 @@
package com.x8bit.bitwarden.data.auth.datasource.sdk.util
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
/**
* Converts the given [Int] to a [PasswordStrength]. A `null` value is returned if this value is
* not in the [0, 4] range.
*/
@Suppress("MagicNumber")
fun Int.toPasswordStrengthOrNull(): PasswordStrength? =
when (this) {
0 -> PasswordStrength.LEVEL_0
1 -> PasswordStrength.LEVEL_1
2 -> PasswordStrength.LEVEL_2
3 -> PasswordStrength.LEVEL_3
4 -> PasswordStrength.LEVEL_4
else -> null
}
/**
* Converts the given [UByte] to a [PasswordStrength]. A `null` value is returned if this value is
* not in the [0, 4] range.
*/
fun UByte.toPasswordStrengthOrNull(): PasswordStrength? =
this.toInt().toPasswordStrengthOrNull()
/**
* Converts the given [UInt] to a [PasswordStrength]. A `null` value is returned if this value is
* not in the [0, 4] range.
*/
fun UInt.toPasswordStrengthOrNull(): PasswordStrength? =
this.toInt().toPasswordStrengthOrNull()
/**
* Converts the given [PasswordStrength] to an [Int].
*/
@Suppress("MagicNumber")
fun PasswordStrength.toInt(): Int =
when (this) {
PasswordStrength.LEVEL_0 -> 0
PasswordStrength.LEVEL_1 -> 1
PasswordStrength.LEVEL_2 -> 2
PasswordStrength.LEVEL_3 -> 3
PasswordStrength.LEVEL_4 -> 4
}
/**
* Converts the given [PasswordStrength] to a [UByte].
*/
@Suppress("MagicNumber")
fun PasswordStrength.toUByte(): UByte =
this.toInt().toUByte()
/**
* Converts the given [PasswordStrength] to a [UInt].
*/
@Suppress("MagicNumber")
fun PasswordStrength.toUInt(): UInt =
this.toInt().toUInt()

View File

@@ -1,6 +1,5 @@
package com.x8bit.bitwarden.data.auth.repository
import com.bitwarden.sdk.Client
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthState
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
@@ -10,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.LoginResult
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
import com.x8bit.bitwarden.data.auth.datasource.network.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
import com.x8bit.bitwarden.data.auth.util.toSdkParams
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
import com.x8bit.bitwarden.data.platform.util.flatMap
@@ -29,7 +29,7 @@ import javax.inject.Singleton
class AuthRepositoryImpl @Inject constructor(
private val accountsService: AccountsService,
private val identityService: IdentityService,
private val bitwardenSdkClient: Client,
private val authSdkSource: AuthSdkSource,
private val authDiskSource: AuthDiskSource,
private val authTokenInterceptor: AuthTokenInterceptor,
) : AuthRepository {
@@ -55,13 +55,13 @@ class AuthRepositoryImpl @Inject constructor(
): LoginResult = accountsService
.preLogin(email = email)
.flatMap {
val passwordHash = bitwardenSdkClient
.auth()
.hashPassword(
email = email,
password = password,
kdfParams = it.kdfParams.toSdkParams(),
)
authSdkSource.hashPassword(
email = email,
password = password,
kdf = it.kdfParams.toSdkParams(),
)
}
.flatMap { passwordHash ->
identityService.getToken(
email = email,
passwordHash = passwordHash,

View File

@@ -8,3 +8,15 @@ inline fun <T, R> Result<T>.flatMap(transform: (T) -> Result<R>): Result<R> =
this.exceptionOrNull()
?.let { Result.failure(it) }
?: transform(this.getOrThrow())
/**
* Returns the given receiver of type [T] as a "success" [Result].
*/
fun <T> T.asSuccess(): Result<T> =
Result.success(this)
/**
* Returns the given [Throwable] as a "failure" [Result].
*/
fun Throwable.asFailure(): Result<Nothing> =
Result.failure(this)