From 6dd783051fc77953bc2e1cb4aa09f3f00af0cb27 Mon Sep 17 00:00:00 2001 From: Dave Severns <149429124+dseverns-livefront@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:53:11 -0500 Subject: [PATCH] =?UTF-8?q?PM-13803=20Check=20to=20see=20if=20an=20existin?= =?UTF-8?q?g=20admin=20request=20is=20pending=20before=20=E2=80=A6=20(#427?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../disk/model/PendingAuthRequestJson.kt | 14 ++- .../auth/manager/AuthRequestManagerImpl.kt | 68 +++++++++++++-- .../manager/model/CreateAuthRequestResult.kt | 5 +- .../LoginWithDeviceViewModel.kt | 4 +- .../datasource/disk/AuthDiskSourceTest.kt | 23 +++-- .../auth/manager/AuthRequestManagerTest.kt | 86 ++++++++++++++++++- .../auth/repository/AuthRepositoryTest.kt | 2 + .../LoginWithDeviceViewModelTest.kt | 59 ++++++++----- 8 files changed, 219 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/model/PendingAuthRequestJson.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/model/PendingAuthRequestJson.kt index a2c1a3d2a..5d4afa7a7 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/model/PendingAuthRequestJson.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/model/PendingAuthRequestJson.kt @@ -7,13 +7,21 @@ import kotlinx.serialization.Serializable * Container for the user's API tokens. * * @property requestId The ID of the pending Auth Request. - * @property requestPrivateKey The private of the pending Auth Request. + * @property requestPrivateKey The private key of the pending Auth Request. + * @property requestAccessCode The access code of the pending Auth Request. + * @property requestFingerprint The fingerprint of the pending Auth Request. */ @Serializable data class PendingAuthRequestJson( - @SerialName("Id") + @SerialName("id") val requestId: String, - @SerialName("PrivateKey") + @SerialName("privateKey") val requestPrivateKey: String, + + @SerialName("accessCode") + val requestAccessCode: String, + + @SerialName("fingerprint") + val requestFingerprint: String, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerImpl.kt index 732a63e14..81f67900c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerImpl.kt @@ -17,6 +17,7 @@ import com.x8bit.bitwarden.data.auth.manager.model.CreateAuthRequestResult import com.x8bit.bitwarden.data.auth.manager.util.isSso import com.x8bit.bitwarden.data.auth.manager.util.toAuthRequestTypeJson import com.x8bit.bitwarden.data.platform.util.asFailure +import com.x8bit.bitwarden.data.platform.util.asSuccess import com.x8bit.bitwarden.data.platform.util.flatMap import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource import kotlinx.coroutines.currentCoroutineContext @@ -65,7 +66,7 @@ class AuthRequestManagerImpl( email: String, authRequestType: AuthRequestType, ): Flow = flow { - val initialResult = createNewAuthRequest( + val initialResult = createNewAuthRequestIfNecessary( email = email, authRequestType = authRequestType.toAuthRequestTypeJson(), ) @@ -74,7 +75,6 @@ class AuthRequestManagerImpl( emit(CreateAuthRequestResult.Error) return@flow } - val authRequestResponse = initialResult.authRequestResponse var authRequest = initialResult.authRequest emit(CreateAuthRequestResult.Update(authRequest)) @@ -84,7 +84,7 @@ class AuthRequestManagerImpl( newAuthRequestService .getAuthRequestUpdate( requestId = authRequest.id, - accessCode = authRequestResponse.accessCode, + accessCode = initialResult.accessCode, isSso = authRequestType.isSso, ) .map { request -> @@ -112,7 +112,8 @@ class AuthRequestManagerImpl( emit( CreateAuthRequestResult.Success( authRequest = updateAuthRequest, - authRequestResponse = authRequestResponse, + privateKey = initialResult.privateKey, + accessCode = initialResult.accessCode, ), ) } @@ -354,6 +355,52 @@ class AuthRequestManagerImpl( ) } + /** + * Creates a new auth request for the given email and returns a [NewAuthRequestData]. + * If the auth request type is [AuthRequestTypeJson.ADMIN_APPROVAL], check for a + * pending auth request and return it if it exists we should return that request. + */ + private suspend fun createNewAuthRequestIfNecessary( + email: String, + authRequestType: AuthRequestTypeJson, + ): Result { + return if (authRequestType == AuthRequestTypeJson.ADMIN_APPROVAL) { + authDiskSource + .getPendingAuthRequest(requireNotNull(activeUserId)) + ?.let { pendingAuthRequest -> + authRequestsService + .getAuthRequest(pendingAuthRequest.requestId) + .map { + NewAuthRequestData( + authRequest = AuthRequest( + id = it.id, + publicKey = it.publicKey, + platform = it.platform, + ipAddress = it.ipAddress, + key = it.key, + masterPasswordHash = it.masterPasswordHash, + creationDate = it.creationDate, + responseDate = it.responseDate, + requestApproved = it.requestApproved ?: false, + originUrl = it.originUrl, + fingerprint = pendingAuthRequest.requestFingerprint, + ), + privateKey = pendingAuthRequest.requestPrivateKey, + accessCode = pendingAuthRequest.requestAccessCode, + ) + .asSuccess() + } + .getOrNull() + } + ?: createNewAuthRequest(email = email, authRequestType = authRequestType) + } else { + createNewAuthRequest( + email = email, + authRequestType = authRequestType, + ) + } + } + /** * Attempts to create a new auth request for the given email and returns a [NewAuthRequestData] * with the [AuthRequest] and [AuthRequestResponse]. @@ -381,6 +428,8 @@ class AuthRequestManagerImpl( pendingAuthRequest = PendingAuthRequestJson( requestId = it.id, requestPrivateKey = authRequestResponse.privateKey, + requestAccessCode = authRequestResponse.accessCode, + requestFingerprint = authRequestResponse.fingerprint, ), ) } @@ -400,7 +449,13 @@ class AuthRequestManagerImpl( fingerprint = authRequestResponse.fingerprint, ) } - .map { NewAuthRequestData(it, authRequestResponse) } + .map { + NewAuthRequestData( + authRequest = it, + privateKey = authRequestResponse.privateKey, + accessCode = authRequestResponse.accessCode, + ) + } } private suspend fun getFingerprintPhrase( @@ -420,5 +475,6 @@ class AuthRequestManagerImpl( */ private data class NewAuthRequestData( val authRequest: AuthRequest, - val authRequestResponse: AuthRequestResponse, + val privateKey: String, + val accessCode: String, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/model/CreateAuthRequestResult.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/model/CreateAuthRequestResult.kt index c424bea1d..f73de5d69 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/model/CreateAuthRequestResult.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/manager/model/CreateAuthRequestResult.kt @@ -1,7 +1,5 @@ package com.x8bit.bitwarden.data.auth.manager.model -import com.bitwarden.core.AuthRequestResponse - /** * Models result of creating a new login approval request. */ @@ -18,7 +16,8 @@ sealed class CreateAuthRequestResult { */ data class Success( val authRequest: AuthRequest, - val authRequestResponse: AuthRequestResponse, + val privateKey: String, + val accessCode: String, ) : CreateAuthRequestResult() /** diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceViewModel.kt index 14bacb994..61fb67058 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceViewModel.kt @@ -116,11 +116,11 @@ class LoginWithDeviceViewModel @Inject constructor( ), dialogState = null, loginData = LoginWithDeviceState.LoginData( - accessCode = result.authRequestResponse.accessCode, + accessCode = result.accessCode, requestId = result.authRequest.id, masterPasswordHash = result.authRequest.masterPasswordHash, asymmetricalKey = requireNotNull(result.authRequest.key), - privateKey = result.authRequestResponse.privateKey, + privateKey = result.privateKey, captchaToken = null, ), ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt index 3d6072287..716b0ae6a 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt @@ -244,6 +244,8 @@ class AuthDiskSourceTest { val pendingAuthRequestJson = PendingAuthRequestJson( requestId = "12345", requestPrivateKey = "67890", + requestFingerprint = "fingerprint", + requestAccessCode = "accessCode", ) authDiskSource.storePendingAuthRequest( userId = userId, @@ -594,15 +596,22 @@ class AuthDiskSourceTest { pendingAdminAuthRequestKey, """ { - "Id": "12345", - "PrivateKey": "67890" + "id": "12345", + "privateKey": "67890", + "fingerprint": "fingerprint", + "accessCode": "accessCode" } """, ) } val actual = authDiskSource.getPendingAuthRequest(userId = mockUserId) assertEquals( - PendingAuthRequestJson(requestId = "12345", requestPrivateKey = "67890"), + PendingAuthRequestJson( + requestId = "12345", + requestPrivateKey = "67890", + requestFingerprint = "fingerprint", + requestAccessCode = "accessCode", + ), actual, ) } @@ -615,6 +624,8 @@ class AuthDiskSourceTest { val pendingAdminAuthRequest = PendingAuthRequestJson( requestId = "12345", requestPrivateKey = "67890", + requestFingerprint = "fingerprint", + requestAccessCode = "accessCode", ) authDiskSource.storePendingAuthRequest( userId = mockUserId, @@ -628,8 +639,10 @@ class AuthDiskSourceTest { json.parseToJsonElement( """ { - "Id": "12345", - "PrivateKey": "67890" + "id": "12345", + "privateKey": "67890", + "fingerprint": "fingerprint", + "accessCode": "accessCode" } """ .trimIndent(), diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerTest.kt index 3afd85cba..88b5ca677 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/manager/AuthRequestManagerTest.kt @@ -162,7 +162,8 @@ class AuthRequestManagerTest { assertEquals( CreateAuthRequestResult.Success( authRequest = authRequest.copy(requestApproved = true), - authRequestResponse = authRequestResponse, + privateKey = authRequestResponse.privateKey, + accessCode = authRequestResponse.accessCode, ), awaitItem(), ) @@ -170,6 +171,87 @@ class AuthRequestManagerTest { } } + @Suppress("MaxLineLength") + @Test + fun `createAuthRequestWithUpdates with a pending admin request Success and getAuthRequestUpdate with approval should emit Success`() = + runTest { + val email = "email@email.com" + val authRequestResponse = AUTH_REQUEST_RESPONSE + val authRequestResponseJson = 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 = false, + originUrl = "www.bitwarden.com", + ) + fakeAuthDiskSource.userState = SINGLE_USER_STATE + fakeAuthDiskSource.storePendingAuthRequest( + userId = USER_ID, + pendingAuthRequest = PendingAuthRequestJson( + requestId = authRequestResponseJson.id, + requestPrivateKey = authRequestResponse.privateKey, + requestAccessCode = authRequestResponse.accessCode, + requestFingerprint = authRequestResponse.fingerprint, + ), + ) + val updatedAuthRequestResponseJson = authRequestResponseJson.copy( + requestApproved = true, + ) + val authRequest = AuthRequest( + id = authRequestResponseJson.id, + publicKey = authRequestResponseJson.publicKey, + platform = authRequestResponseJson.platform, + ipAddress = authRequestResponseJson.ipAddress, + key = authRequestResponseJson.key, + masterPasswordHash = authRequestResponseJson.masterPasswordHash, + creationDate = authRequestResponseJson.creationDate, + responseDate = authRequestResponseJson.responseDate, + requestApproved = authRequestResponseJson.requestApproved ?: false, + originUrl = authRequestResponseJson.originUrl, + fingerprint = authRequestResponse.fingerprint, + ) + coEvery { + authRequestsService.getAuthRequest( + requestId = authRequest.id, + ) + } returns authRequestResponseJson.asSuccess() + coEvery { + newAuthRequestService.getAuthRequestUpdate( + requestId = authRequest.id, + accessCode = authRequestResponse.accessCode, + isSso = true, + ) + } returnsMany listOf( + authRequestResponseJson.asSuccess(), + updatedAuthRequestResponseJson.asSuccess(), + ) + + repository + .createAuthRequestWithUpdates( + email = email, + authRequestType = AuthRequestType.SSO_ADMIN_APPROVAL, + ) + .test { + assertEquals(CreateAuthRequestResult.Update(authRequest), awaitItem()) + assertEquals(CreateAuthRequestResult.Update(authRequest), awaitItem()) + assertEquals( + CreateAuthRequestResult.Success( + authRequest = authRequest.copy(requestApproved = true), + privateKey = authRequestResponse.privateKey, + accessCode = authRequestResponse.accessCode, + ), + awaitItem(), + ) + awaitComplete() + } + coVerify(exactly = 0) { authSdkSource.getNewAuthRequest(any()) } + } + @Suppress("MaxLineLength") @Test fun `createAuthRequestWithUpdates with createNewAuthRequest Success and getAuthRequestUpdate with response date and no approval should emit Declined`() = @@ -305,6 +387,8 @@ class AuthRequestManagerTest { pendingAuthRequest = PendingAuthRequestJson( requestId = authRequestResponseJson.id, requestPrivateKey = authRequestResponse.privateKey, + requestAccessCode = authRequestResponse.accessCode, + requestFingerprint = authRequestResponse.fingerprint, ), ) assertEquals(CreateAuthRequestResult.Expired, awaitItem()) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt index 887c7139a..944138f6e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt @@ -3566,6 +3566,8 @@ class AuthRepositoryTest { val pendingAuthRequest = PendingAuthRequestJson( requestId = "requestId", requestPrivateKey = "requestPrivateKey", + requestFingerprint = "fingerprint", + requestAccessCode = "accessCode", ) val successResponse = GET_TOKEN_RESPONSE_SUCCESS.copy( key = null, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceViewModelTest.kt index 8b37fd22c..ebb9a6b5b 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/loginwithdevice/LoginWithDeviceViewModelTest.kt @@ -2,7 +2,6 @@ package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test -import com.bitwarden.core.AuthRequestResponse import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.manager.model.AuthRequest import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestType @@ -166,7 +165,11 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() { viewModel.stateFlow.test { assertEquals(DEFAULT_STATE, awaitItem()) mutableCreateAuthRequestWithUpdatesFlow.tryEmit( - CreateAuthRequestResult.Success(AUTH_REQUEST, AUTH_REQUEST_RESPONSE), + CreateAuthRequestResult.Success( + authRequest = AUTH_REQUEST, + privateKey = AUTH_REQUEST_PRIVATE_KEY, + accessCode = AUTH_REQUEST_ACCESS_CODE, + ), ) assertEquals( DEFAULT_STATE.copy( @@ -196,9 +199,9 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() { authRepository.login( email = EMAIL, requestId = AUTH_REQUEST.id, - accessCode = AUTH_REQUEST_RESPONSE.accessCode, + accessCode = AUTH_REQUEST_ACCESS_CODE, asymmetricalKey = requireNotNull(AUTH_REQUEST.key), - requestPrivateKey = AUTH_REQUEST_RESPONSE.privateKey, + requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY, masterPasswordHash = AUTH_REQUEST.masterPasswordHash, captchaToken = null, ) @@ -227,7 +230,11 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() { viewModel.stateFlow.test { assertEquals(initialState, awaitItem()) mutableCreateAuthRequestWithUpdatesFlow.tryEmit( - CreateAuthRequestResult.Success(AUTH_REQUEST, AUTH_REQUEST_RESPONSE), + CreateAuthRequestResult.Success( + authRequest = AUTH_REQUEST, + privateKey = AUTH_REQUEST_PRIVATE_KEY, + accessCode = AUTH_REQUEST_ACCESS_CODE, + ), ) assertEquals( initialState.copy( @@ -279,7 +286,11 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() { val viewModel = createViewModel() viewModel.eventFlow.test { mutableCreateAuthRequestWithUpdatesFlow.tryEmit( - CreateAuthRequestResult.Success(AUTH_REQUEST, AUTH_REQUEST_RESPONSE), + CreateAuthRequestResult.Success( + authRequest = AUTH_REQUEST, + privateKey = AUTH_REQUEST_PRIVATE_KEY, + accessCode = AUTH_REQUEST_ACCESS_CODE, + ), ) assertEquals( LoginWithDeviceEvent.NavigateToTwoFactorLogin(emailAddress = EMAIL), @@ -291,9 +302,9 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() { authRepository.login( email = EMAIL, requestId = AUTH_REQUEST.id, - accessCode = AUTH_REQUEST_RESPONSE.accessCode, + accessCode = AUTH_REQUEST_ACCESS_CODE, asymmetricalKey = requireNotNull(AUTH_REQUEST.key), - requestPrivateKey = AUTH_REQUEST_RESPONSE.privateKey, + requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY, masterPasswordHash = AUTH_REQUEST.masterPasswordHash, captchaToken = null, ) @@ -319,7 +330,11 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() { viewModel.stateFlow.test { assertEquals(DEFAULT_STATE, awaitItem()) mutableCreateAuthRequestWithUpdatesFlow.tryEmit( - CreateAuthRequestResult.Success(AUTH_REQUEST, AUTH_REQUEST_RESPONSE), + CreateAuthRequestResult.Success( + authRequest = AUTH_REQUEST, + privateKey = AUTH_REQUEST_PRIVATE_KEY, + accessCode = AUTH_REQUEST_ACCESS_CODE, + ), ) assertEquals( DEFAULT_STATE.copy( @@ -353,9 +368,9 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() { authRepository.login( email = EMAIL, requestId = AUTH_REQUEST.id, - accessCode = AUTH_REQUEST_RESPONSE.accessCode, + accessCode = AUTH_REQUEST_ACCESS_CODE, asymmetricalKey = requireNotNull(AUTH_REQUEST.key), - requestPrivateKey = AUTH_REQUEST_RESPONSE.privateKey, + requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY, masterPasswordHash = AUTH_REQUEST.masterPasswordHash, captchaToken = null, ) @@ -382,7 +397,11 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() { viewModel.stateFlow.test { assertEquals(DEFAULT_STATE, awaitItem()) mutableCreateAuthRequestWithUpdatesFlow.tryEmit( - CreateAuthRequestResult.Success(AUTH_REQUEST, AUTH_REQUEST_RESPONSE), + CreateAuthRequestResult.Success( + authRequest = AUTH_REQUEST, + privateKey = AUTH_REQUEST_PRIVATE_KEY, + accessCode = AUTH_REQUEST_ACCESS_CODE, + ), ) assertEquals( DEFAULT_STATE.copy( @@ -416,9 +435,9 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() { authRepository.login( email = EMAIL, requestId = AUTH_REQUEST.id, - accessCode = AUTH_REQUEST_RESPONSE.accessCode, + accessCode = AUTH_REQUEST_ACCESS_CODE, asymmetricalKey = requireNotNull(AUTH_REQUEST.key), - requestPrivateKey = AUTH_REQUEST_RESPONSE.privateKey, + requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY, masterPasswordHash = AUTH_REQUEST.masterPasswordHash, captchaToken = null, ) @@ -474,9 +493,9 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() { authRepository.login( email = EMAIL, requestId = AUTH_REQUEST.id, - accessCode = AUTH_REQUEST_RESPONSE.accessCode, + accessCode = AUTH_REQUEST_ACCESS_CODE, asymmetricalKey = requireNotNull(AUTH_REQUEST.key), - requestPrivateKey = AUTH_REQUEST_RESPONSE.privateKey, + requestPrivateKey = AUTH_REQUEST_PRIVATE_KEY, masterPasswordHash = AUTH_REQUEST.masterPasswordHash, captchaToken = captchaToken, ) @@ -603,12 +622,8 @@ private val AUTH_REQUEST = AuthRequest( fingerprint = FINGERPRINT, ) -private val AUTH_REQUEST_RESPONSE = AuthRequestResponse( - privateKey = "private_key", - publicKey = "public_key", - accessCode = "accessCode", - fingerprint = "fingerprint", -) +private const val AUTH_REQUEST_ACCESS_CODE = "accessCode" +private const val AUTH_REQUEST_PRIVATE_KEY = "private_key" private val DEFAULT_LOGIN_DATA = LoginWithDeviceState.LoginData( accessCode = "accessCode",