mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-1291: Initiate Login with Device flow (#791)
This commit is contained in:
parent
52acc2fa47
commit
0818638273
16 changed files with 407 additions and 3 deletions
|
@ -1,13 +1,24 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /auth-requests API.
|
||||
*/
|
||||
interface AuthRequestsApi {
|
||||
|
||||
/**
|
||||
* Notifies the server of a new authentication request.
|
||||
*/
|
||||
@POST("/auth-requests")
|
||||
suspend fun createAuthRequest(
|
||||
@Body body: AuthRequestRequestJson,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
|
||||
/**
|
||||
* Gets a list of auth requests for this device.
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,8 @@ import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedSe
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestServiceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
@ -74,4 +76,12 @@ object AuthNetworkModule {
|
|||
.build()
|
||||
.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesNewAuthRequestService(
|
||||
retrofits: Retrofits,
|
||||
): NewAuthRequestService = NewAuthRequestServiceImpl(
|
||||
authRequestsApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Request body for creating an auth request.
|
||||
*/
|
||||
@Serializable
|
||||
data class AuthRequestRequestJson(
|
||||
@SerialName("email")
|
||||
val email: String,
|
||||
|
||||
@SerialName("publicKey")
|
||||
val publicKey: String,
|
||||
|
||||
@SerialName("deviceIdentifier")
|
||||
val deviceId: String,
|
||||
|
||||
@SerialName("accessCode")
|
||||
val accessCode: String,
|
||||
|
||||
@SerialName("type")
|
||||
val type: AuthRequestTypeJson,
|
||||
|
||||
@SerialName("fingerprintPhrase")
|
||||
val fingerprint: String,
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the different types of auth requests.
|
||||
*/
|
||||
@Serializable(AuthRequestTypeSerializer::class)
|
||||
enum class AuthRequestTypeJson {
|
||||
@SerialName("0")
|
||||
LOGIN_WITH_DEVICE,
|
||||
}
|
||||
|
||||
@Keep
|
||||
private class AuthRequestTypeSerializer :
|
||||
BaseEnumeratedIntSerializer<AuthRequestTypeJson>(AuthRequestTypeJson.entries.toTypedArray())
|
|
@ -41,6 +41,7 @@ data class AuthRequestsResponseJson(
|
|||
|
||||
@SerialName("requestIpAddress")
|
||||
val ipAddress: String,
|
||||
|
||||
@SerialName("key")
|
||||
val key: String?,
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsRespon
|
|||
class AuthRequestsServiceImpl(
|
||||
private val authRequestsApi: AuthRequestsApi,
|
||||
) : AuthRequestsService {
|
||||
|
||||
override suspend fun getAuthRequests(): Result<AuthRequestsResponseJson> =
|
||||
authRequestsApi.getAuthRequests()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||
|
||||
/**
|
||||
* Provides an API for creating a new authentication request.
|
||||
*/
|
||||
interface NewAuthRequestService {
|
||||
/**
|
||||
* Informs the server of a new auth request in order to notify approving devices.
|
||||
*/
|
||||
suspend fun createAuthRequest(
|
||||
email: String,
|
||||
publicKey: String,
|
||||
deviceId: String,
|
||||
accessCode: String,
|
||||
fingerprint: String,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.AuthRequestsApi
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||
|
||||
/**
|
||||
* The default implementation of the [NewAuthRequestService].
|
||||
*/
|
||||
class NewAuthRequestServiceImpl(
|
||||
private val authRequestsApi: AuthRequestsApi,
|
||||
) : NewAuthRequestService {
|
||||
override suspend fun createAuthRequest(
|
||||
email: String,
|
||||
publicKey: String,
|
||||
deviceId: String,
|
||||
accessCode: String,
|
||||
fingerprint: String,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest> =
|
||||
authRequestsApi.createAuthRequest(
|
||||
AuthRequestRequestJson(
|
||||
email = email,
|
||||
publicKey = publicKey,
|
||||
deviceId = deviceId,
|
||||
accessCode = accessCode,
|
||||
fingerprint = fingerprint,
|
||||
type = AuthRequestTypeJson.LOGIN_WITH_DEVICE,
|
||||
),
|
||||
)
|
||||
}
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.repository
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequest
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequestResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequestsResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
|
@ -157,6 +158,11 @@ interface AuthRepository : AuthenticatorProvider {
|
|||
*/
|
||||
fun setSsoCallbackResult(result: SsoCallbackResult)
|
||||
|
||||
/**
|
||||
* Creates a new authentication request.
|
||||
*/
|
||||
suspend fun createAuthRequest(email: String): AuthRequestResult
|
||||
|
||||
/**
|
||||
* Get a list of the current user's [AuthRequest]s.
|
||||
*/
|
||||
|
|
|
@ -21,10 +21,12 @@ import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsServ
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfTypeJson
|
||||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequest
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequestResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequestsResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
|
@ -80,6 +82,7 @@ class AuthRepositoryImpl(
|
|||
private val devicesService: DevicesService,
|
||||
private val haveIBeenPwnedService: HaveIBeenPwnedService,
|
||||
private val identityService: IdentityService,
|
||||
private val newAuthRequestService: NewAuthRequestService,
|
||||
private val authSdkSource: AuthSdkSource,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
|
@ -555,6 +558,40 @@ class AuthRepositoryImpl(
|
|||
mutableSsoCallbackResultFlow.tryEmit(result)
|
||||
}
|
||||
|
||||
override suspend fun createAuthRequest(
|
||||
email: String,
|
||||
): AuthRequestResult =
|
||||
authSdkSource
|
||||
.getNewAuthRequest(email)
|
||||
.flatMap { authRequest ->
|
||||
newAuthRequestService.createAuthRequest(
|
||||
email = email,
|
||||
publicKey = authRequest.publicKey,
|
||||
deviceId = authDiskSource.uniqueAppId,
|
||||
accessCode = authRequest.accessCode,
|
||||
fingerprint = authRequest.fingerprint,
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = { AuthRequestResult.Error },
|
||||
onSuccess = { request ->
|
||||
AuthRequestResult.Success(
|
||||
authRequest = AuthRequest(
|
||||
id = request.id,
|
||||
publicKey = request.publicKey,
|
||||
platform = request.platform,
|
||||
ipAddress = request.ipAddress,
|
||||
key = request.key,
|
||||
masterPasswordHash = request.masterPasswordHash,
|
||||
creationDate = request.creationDate,
|
||||
responseDate = request.responseDate,
|
||||
requestApproved = request.requestApproved ?: false,
|
||||
originUrl = request.originUrl,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
override suspend fun getAuthRequests(): AuthRequestsResult =
|
||||
authRequestsService.getAuthRequests()
|
||||
.fold(
|
||||
|
@ -582,7 +619,8 @@ class AuthRepositoryImpl(
|
|||
override suspend fun getFingerprintPhrase(
|
||||
email: String,
|
||||
): UserFingerprintResult =
|
||||
authSdkSource.getNewAuthRequest(email)
|
||||
authSdkSource
|
||||
.getNewAuthRequest(email)
|
||||
.flatMap { requestResponse ->
|
||||
authSdkSource
|
||||
.getUserFingerprint(
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsServ
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
|
@ -36,6 +37,7 @@ object AuthRepositoryModule {
|
|||
devicesService: DevicesService,
|
||||
identityService: IdentityService,
|
||||
haveIBeenPwnedService: HaveIBeenPwnedService,
|
||||
newAuthRequestService: NewAuthRequestService,
|
||||
authSdkSource: AuthSdkSource,
|
||||
authDiskSource: AuthDiskSource,
|
||||
dispatchers: DispatcherManager,
|
||||
|
@ -48,6 +50,7 @@ object AuthRepositoryModule {
|
|||
authRequestsService = authRequestsService,
|
||||
devicesService = devicesService,
|
||||
identityService = identityService,
|
||||
newAuthRequestService = newAuthRequestService,
|
||||
authSdkSource = authSdkSource,
|
||||
authDiskSource = authDiskSource,
|
||||
haveIBeenPwnedService = haveIBeenPwnedService,
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.model
|
||||
|
||||
/**
|
||||
* Models result of creating a new login approval request.
|
||||
*/
|
||||
sealed class AuthRequestResult {
|
||||
/**
|
||||
* Models the data returned when creating an auth request.
|
||||
*/
|
||||
data class Success(
|
||||
val authRequest: AuthRequest,
|
||||
) : AuthRequestResult()
|
||||
|
||||
/**
|
||||
* There was an error getting the user's auth requests.
|
||||
*/
|
||||
data object Error : AuthRequestResult()
|
||||
}
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequestResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserFingerprintResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
|
@ -32,6 +33,8 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||
),
|
||||
) {
|
||||
init {
|
||||
sendNewAuthRequest()
|
||||
|
||||
viewModelScope.launch {
|
||||
trySendAction(
|
||||
LoginWithDeviceAction.Internal.FingerprintPhraseReceived(
|
||||
|
@ -47,6 +50,10 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||
LoginWithDeviceAction.ResendNotificationClick -> handleResendNotificationClicked()
|
||||
LoginWithDeviceAction.ViewAllLogInOptionsClick -> handleViewAllLogInOptionsClicked()
|
||||
|
||||
is LoginWithDeviceAction.Internal.NewAuthRequestResultReceive -> {
|
||||
handleNewAuthRequestResultReceived(action)
|
||||
}
|
||||
|
||||
is LoginWithDeviceAction.Internal.FingerprintPhraseReceived -> {
|
||||
handleFingerprintPhraseReceived(action)
|
||||
}
|
||||
|
@ -66,6 +73,14 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||
sendEvent(LoginWithDeviceEvent.NavigateBack)
|
||||
}
|
||||
|
||||
private fun handleNewAuthRequestResultReceived(
|
||||
action: LoginWithDeviceAction.Internal.NewAuthRequestResultReceive,
|
||||
) {
|
||||
if (action.result is AuthRequestResult.Error) {
|
||||
// TODO BIT-1563 handle error
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFingerprintPhraseReceived(
|
||||
action: LoginWithDeviceAction.Internal.FingerprintPhraseReceived,
|
||||
) {
|
||||
|
@ -91,6 +106,18 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendNewAuthRequest() {
|
||||
viewModelScope.launch {
|
||||
trySendAction(
|
||||
LoginWithDeviceAction.Internal.NewAuthRequestResultReceive(
|
||||
result = authRepository.createAuthRequest(
|
||||
email = state.emailAddress,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,6 +203,13 @@ sealed class LoginWithDeviceAction {
|
|||
* Models actions for internal use by the view model.
|
||||
*/
|
||||
sealed class Internal : LoginWithDeviceAction() {
|
||||
/**
|
||||
* A new auth request result was received.
|
||||
*/
|
||||
data class NewAuthRequestResultReceive(
|
||||
val result: AuthRequestResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* A fingerprint phrase for this user has been received.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.AuthRequestsApi
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.create
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
class NewAuthRequestServiceTest : BaseServiceTest() {
|
||||
|
||||
private val authRequestsApi: AuthRequestsApi = retrofit.create()
|
||||
private val service = NewAuthRequestServiceImpl(
|
||||
authRequestsApi = authRequestsApi,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `createAuthRequest when request response is Failure should return Failure`() = runTest {
|
||||
val response = MockResponse().setResponseCode(400)
|
||||
server.enqueue(response)
|
||||
val actual = service.createAuthRequest(
|
||||
email = "test@gmail.com",
|
||||
publicKey = "1234",
|
||||
deviceId = "4321",
|
||||
accessCode = "accessCode",
|
||||
fingerprint = "fingerprint",
|
||||
)
|
||||
assertTrue(actual.isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createAuthRequest when request response is Success should return Success`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"id": "1",
|
||||
"publicKey": "2",
|
||||
"requestDeviceType": "Android",
|
||||
"requestIpAddress": "1.0.0.1",
|
||||
"key": "key",
|
||||
"masterPasswordHash": "verySecureHash",
|
||||
"creationDate": "2024-09-13T01:00:00.00Z",
|
||||
"requestApproved": true,
|
||||
"origin": "www.bitwarden.com"
|
||||
}
|
||||
"""
|
||||
val expected = AuthRequestsResponseJson.AuthRequest(
|
||||
id = "1",
|
||||
publicKey = "2",
|
||||
platform = "Android",
|
||||
ipAddress = "1.0.0.1",
|
||||
key = "key",
|
||||
masterPasswordHash = "verySecureHash",
|
||||
creationDate = ZonedDateTime.parse("2024-09-13T01:00:00.00Z"),
|
||||
responseDate = null,
|
||||
requestApproved = true,
|
||||
originUrl = "www.bitwarden.com",
|
||||
)
|
||||
val response = MockResponse().setBody(json).setResponseCode(200)
|
||||
server.enqueue(response)
|
||||
val actual = service.createAuthRequest(
|
||||
email = "test@gmail.com",
|
||||
publicKey = "1234",
|
||||
deviceId = "4321",
|
||||
accessCode = "accessCode",
|
||||
fingerprint = "fingerprint",
|
||||
)
|
||||
assertEquals(Result.success(expected), actual)
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsServ
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_0
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_1
|
||||
|
@ -36,6 +37,7 @@ import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL
|
|||
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_4
|
||||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequest
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequestResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequestsResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
|
@ -98,6 +100,7 @@ class AuthRepositoryTest {
|
|||
private val devicesService: DevicesService = mockk()
|
||||
private val identityService: IdentityService = mockk()
|
||||
private val haveIBeenPwnedService: HaveIBeenPwnedService = mockk()
|
||||
private val newAuthRequestService: NewAuthRequestService = mockk()
|
||||
private val mutableVaultStateFlow = MutableStateFlow(VAULT_STATE)
|
||||
private val vaultRepository: VaultRepository = mockk {
|
||||
every { vaultStateFlow } returns mutableVaultStateFlow
|
||||
|
@ -114,6 +117,11 @@ class AuthRepositoryTest {
|
|||
every { setDefaultsIfNecessary(any()) } just runs
|
||||
}
|
||||
private val authSdkSource = mockk<AuthSdkSource> {
|
||||
coEvery {
|
||||
getNewAuthRequest(
|
||||
email = EMAIL,
|
||||
)
|
||||
} returns Result.success(AUTH_REQUEST_RESPONSE)
|
||||
coEvery {
|
||||
hashPassword(
|
||||
email = EMAIL,
|
||||
|
@ -151,6 +159,7 @@ class AuthRepositoryTest {
|
|||
devicesService = devicesService,
|
||||
identityService = identityService,
|
||||
haveIBeenPwnedService = haveIBeenPwnedService,
|
||||
newAuthRequestService = newAuthRequestService,
|
||||
authSdkSource = authSdkSource,
|
||||
authDiskSource = fakeAuthDiskSource,
|
||||
environmentRepository = fakeEnvironmentRepository,
|
||||
|
@ -1605,6 +1614,93 @@ class AuthRepositoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createAuthRequest should return failure when service returns failure`() = runTest {
|
||||
val accessCode = "accessCode"
|
||||
val fingerprint = "fingerprint"
|
||||
coEvery {
|
||||
newAuthRequestService.createAuthRequest(
|
||||
email = EMAIL,
|
||||
publicKey = PUBLIC_KEY,
|
||||
deviceId = UNIQUE_APP_ID,
|
||||
accessCode = accessCode,
|
||||
fingerprint = fingerprint,
|
||||
)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
|
||||
val result = repository.createAuthRequest(
|
||||
email = EMAIL,
|
||||
)
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
newAuthRequestService.createAuthRequest(
|
||||
email = EMAIL,
|
||||
publicKey = PUBLIC_KEY,
|
||||
deviceId = UNIQUE_APP_ID,
|
||||
accessCode = accessCode,
|
||||
fingerprint = fingerprint,
|
||||
)
|
||||
}
|
||||
assertEquals(AuthRequestResult.Error, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createAuthRequest should return success when service returns success`() = runTest {
|
||||
val accessCode = "accessCode"
|
||||
val fingerprint = "fingerprint"
|
||||
|
||||
val responseJson = AuthRequestsResponseJson.AuthRequest(
|
||||
id = "1",
|
||||
publicKey = PUBLIC_KEY,
|
||||
platform = "Android",
|
||||
ipAddress = "192.168.0.1",
|
||||
key = "public",
|
||||
masterPasswordHash = "verySecureHash",
|
||||
creationDate = ZonedDateTime.parse("2024-09-13T00:00Z"),
|
||||
responseDate = null,
|
||||
requestApproved = true,
|
||||
originUrl = "www.bitwarden.com",
|
||||
)
|
||||
val expected = AuthRequestResult.Success(
|
||||
authRequest = AuthRequest(
|
||||
id = "1",
|
||||
publicKey = PUBLIC_KEY,
|
||||
platform = "Android",
|
||||
ipAddress = "192.168.0.1",
|
||||
key = "public",
|
||||
masterPasswordHash = "verySecureHash",
|
||||
creationDate = ZonedDateTime.parse("2024-09-13T00:00Z"),
|
||||
responseDate = null,
|
||||
requestApproved = true,
|
||||
originUrl = "www.bitwarden.com",
|
||||
),
|
||||
)
|
||||
coEvery {
|
||||
newAuthRequestService.createAuthRequest(
|
||||
email = EMAIL,
|
||||
publicKey = PUBLIC_KEY,
|
||||
deviceId = UNIQUE_APP_ID,
|
||||
accessCode = accessCode,
|
||||
fingerprint = fingerprint,
|
||||
)
|
||||
} returns responseJson.asSuccess()
|
||||
|
||||
val result = repository.createAuthRequest(
|
||||
email = EMAIL,
|
||||
)
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
newAuthRequestService.createAuthRequest(
|
||||
email = EMAIL,
|
||||
publicKey = PUBLIC_KEY,
|
||||
deviceId = UNIQUE_APP_ID,
|
||||
accessCode = accessCode,
|
||||
fingerprint = fingerprint,
|
||||
)
|
||||
}
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAuthRequests should return failure when service returns failure`() = runTest {
|
||||
coEvery {
|
||||
|
@ -1869,6 +1965,12 @@ class AuthRepositoryTest {
|
|||
private val PRE_LOGIN_SUCCESS = PreLoginResponseJson(
|
||||
kdfParams = PreLoginResponseJson.KdfParams.Pbkdf2(iterations = 1u),
|
||||
)
|
||||
private val AUTH_REQUEST_RESPONSE = AuthRequestResponse(
|
||||
privateKey = PRIVATE_KEY,
|
||||
publicKey = PUBLIC_KEY,
|
||||
accessCode = "accessCode",
|
||||
fingerprint = "fingerprint",
|
||||
)
|
||||
private val REFRESH_TOKEN_RESPONSE_JSON = RefreshTokenResponseJson(
|
||||
accessToken = ACCESS_TOKEN_2,
|
||||
expiresIn = 3600,
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthRequestResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserFingerprintResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
@ -20,6 +21,9 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
|
|||
coEvery {
|
||||
getFingerprintPhrase(EMAIL)
|
||||
} returns UserFingerprintResult.Success("initialFingerprint")
|
||||
coEvery {
|
||||
createAuthRequest(EMAIL)
|
||||
} returns mockk<AuthRequestResult.Success>()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -28,12 +32,17 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
|
|||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
}
|
||||
coVerify { authRepository.createAuthRequest(EMAIL) }
|
||||
coVerify { authRepository.getFingerprintPhrase(EMAIL) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct when set`() = runTest {
|
||||
val newEmail = "newEmail@gmail.com"
|
||||
|
||||
coEvery {
|
||||
authRepository.createAuthRequest(newEmail)
|
||||
} returns mockk<AuthRequestResult.Success>()
|
||||
coEvery {
|
||||
authRepository.getFingerprintPhrase(newEmail)
|
||||
} returns UserFingerprintResult.Success("initialFingerprint")
|
||||
|
@ -47,7 +56,10 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
|
|||
viewModel.stateFlow.test {
|
||||
assertEquals(state, awaitItem())
|
||||
}
|
||||
coVerify { authRepository.getFingerprintPhrase(newEmail) }
|
||||
coVerify {
|
||||
authRepository.createAuthRequest(newEmail)
|
||||
authRepository.getFingerprintPhrase(newEmail)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Add table
Reference in a new issue