Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.AuthState
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
import com.x8bit.bitwarden.data.auth.repository.model.DeleteAccountResult
import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
import com.x8bit.bitwarden.data.auth.repository.model.GetDeviceResult
import com.x8bit.bitwarden.data.auth.repository.model.GetDevicesResult
import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
import com.x8bit.bitwarden.data.auth.repository.model.LeaveOrganizationResult
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
Expand Down Expand Up @@ -354,6 +356,16 @@ interface AuthRepository :
*/
fun setCookieCallbackResult(result: CookieCallbackResult)

/**
* Retrieves all devices registered to the current user.
*/
suspend fun getDevices(): GetDevicesResult

/**
* Retrieves the device matching this app's unique identifier.
*/
suspend fun getDeviceByIdentifier(): GetDeviceResult

/**
* Get a [Boolean] indicating whether this is a known device.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.AuthState
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
import com.x8bit.bitwarden.data.auth.repository.model.DeleteAccountResult
import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
import com.x8bit.bitwarden.data.auth.repository.model.GetDeviceResult
import com.x8bit.bitwarden.data.auth.repository.model.GetDevicesResult
import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
import com.x8bit.bitwarden.data.auth.repository.model.LeaveOrganizationResult
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
Expand Down Expand Up @@ -101,6 +103,7 @@ import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult
import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow
import com.x8bit.bitwarden.data.auth.repository.util.policyInformation
import com.x8bit.bitwarden.data.auth.repository.util.toDeviceInfo
import com.x8bit.bitwarden.data.auth.repository.util.toOrganizations
import com.x8bit.bitwarden.data.auth.repository.util.toRemovedPasswordUserStateJson
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
Expand Down Expand Up @@ -1267,6 +1270,32 @@ class AuthRepositoryImpl(
mutableCookieCallbackResultFlow.tryEmit(result)
}

override suspend fun getDevices(): GetDevicesResult =
devicesService
.getDevices()
.fold(
onFailure = { GetDevicesResult.Error },
onSuccess = { response ->
GetDevicesResult.Success(
devices = response.devices.map { json ->
json.toDeviceInfo()
},
)
},
)

override suspend fun getDeviceByIdentifier(): GetDeviceResult =
devicesService
.getDeviceByIdentifier(authDiskSource.uniqueAppId)
.fold(
onFailure = { GetDeviceResult.Error },
onSuccess = { json ->
GetDeviceResult.Success(
device = json.toDeviceInfo(),
)
},
)

override suspend fun getIsKnownDevice(emailAddress: String): KnownDeviceResult =
devicesService
.getIsKnownDevice(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.x8bit.bitwarden.data.auth.repository.model

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.time.Instant

/**
* Domain model for a device registered to the current user.
*
* @property id The unique identifier of the device.
* @property name The name of the device.
* @property identifier The unique identifier of the device.
* @property type The type of the device.
* @property isTrusted Whether this device is trusted.
* @property creationDate The date and time on which this device was created.
* @property lastActivityDate The date and time of the device's last activity, if available.
* @property pendingAuthRequest The pending auth request for this device, if any.
*/
@Parcelize
data class DeviceInfo(
val id: String,
val name: String,
val identifier: String,
val type: Int,
val isTrusted: Boolean,
val creationDate: Instant,
val lastActivityDate: Instant?,
val pendingAuthRequest: DevicePendingAuthRequest?,
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.x8bit.bitwarden.data.auth.repository.model

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.time.Instant

/**
* Domain model for a pending auth request associated with a device.
*
* @property id The unique identifier of the pending auth request.
* @property creationDate The date and time on which this auth request was created.
*/
@Parcelize
data class DevicePendingAuthRequest(
val id: String,
val creationDate: Instant,
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.x8bit.bitwarden.data.auth.repository.model

/**
* Models result of retrieving the device matching this app's unique identifier.
*/
sealed class GetDeviceResult {
/**
* Contains the [DeviceInfo] for the current device.
*/
data class Success(val device: DeviceInfo) : GetDeviceResult()

/**
* There was an error retrieving the device.
*/
data object Error : GetDeviceResult()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.x8bit.bitwarden.data.auth.repository.model

/**
* Models result of retrieving all devices registered to the current user.
*/
sealed class GetDevicesResult {
/**
* Contains the list of [DeviceInfo] for the current user's registered devices.
*/
data class Success(val devices: List<DeviceInfo>) : GetDevicesResult()

/**
* There was an error retrieving the devices.
*/
data object Error : GetDevicesResult()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.x8bit.bitwarden.data.auth.repository.util

import com.bitwarden.network.model.DeviceResponseJson
import com.x8bit.bitwarden.data.auth.repository.model.DeviceInfo
import com.x8bit.bitwarden.data.auth.repository.model.DevicePendingAuthRequest

/**
* Maps the given [DeviceResponseJson] to a [DeviceInfo].
*/
fun DeviceResponseJson.toDeviceInfo(): DeviceInfo =
DeviceInfo(
id = id,
name = name,
identifier = identifier,
type = type,
isTrusted = isTrusted,
creationDate = creationDate,
lastActivityDate = lastActivityDate,
pendingAuthRequest = devicePendingAuthRequest?.let {
DevicePendingAuthRequest(
id = it.id,
creationDate = it.creationDate,
)
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ fun NavGraphBuilder.settingsGraph(
onNavigateToImportLogins: () -> Unit,
onNavigateToImportItems: () -> Unit,
onNavigateToAboutPrivilegedApps: () -> Unit,
onNavigateToManageDevices: () -> Unit,
) {
navigation<SettingsGraphRoute>(
startDestination = SettingsRoute.Standard,
Expand All @@ -144,6 +145,7 @@ fun NavGraphBuilder.settingsGraph(
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
onNavigateToPendingRequests = onNavigateToPendingRequests,
onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen,
onNavigateToManageDevices = onNavigateToManageDevices,
)
appearanceDestination(
isPreAuth = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ fun NavGraphBuilder.accountSecurityDestination(
onNavigateToDeleteAccount: () -> Unit,
onNavigateToPendingRequests: () -> Unit,
onNavigateToSetupUnlockScreen: () -> Unit,
onNavigateToManageDevices: () -> Unit,
) {
composableWithPushTransitions<AccountSecurityRoute> {
AccountSecurityScreen(
onNavigateBack = onNavigateBack,
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
onNavigateToPendingRequests = onNavigateToPendingRequests,
onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen,
onNavigateToManageDevices = onNavigateToManageDevices,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ fun AccountSecurityScreen(
onNavigateToDeleteAccount: () -> Unit,
onNavigateToPendingRequests: () -> Unit,
onNavigateToSetupUnlockScreen: () -> Unit,
onNavigateToManageDevices: () -> Unit,
viewModel: AccountSecurityViewModel = hiltViewModel(),
biometricsManager: BiometricsManager = LocalBiometricsManager.current,
intentManager: IntentManager = LocalIntentManager.current,
Expand Down Expand Up @@ -118,6 +119,8 @@ fun AccountSecurityScreen(
intentManager.launchUri(event.url.toUri())
}

is AccountSecurityEvent.NavigateToManageDevices -> onNavigateToManageDevices()

is AccountSecurityEvent.ShowBiometricsPrompt -> {
showBiometricsPrompt = true
biometricsManager.promptBiometrics(
Expand Down Expand Up @@ -192,32 +195,36 @@ fun AccountSecurityScreen(
)
}

BitwardenListHeaderText(
label = stringResource(id = BitwardenString.approve_login_requests),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(height = 8.dp))
BitwardenTextRow(
text = stringResource(id = BitwardenString.pending_log_in_requests),
onClick = {
viewModel.trySendAction(AccountSecurityAction.PendingLoginRequestsClick)
},
cardStyle = CardStyle.Full,
modifier = Modifier
.testTag("PendingLogInRequestsLabel")
.standardHorizontalMargin()
.fillMaxWidth(),
)
if (!state.isManageDevicesEnabled) {
BitwardenListHeaderText(
label = stringResource(id = BitwardenString.approve_login_requests),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(height = 8.dp))
BitwardenTextRow(
text = stringResource(id = BitwardenString.pending_log_in_requests),
onClick = {
viewModel.trySendAction(AccountSecurityAction.PendingLoginRequestsClick)
},
cardStyle = CardStyle.Full,
modifier = Modifier
.testTag("PendingLogInRequestsLabel")
.standardHorizontalMargin()
.fillMaxWidth(),
)
}

val biometricSupportStatus = biometricsManager.biometricSupportStatus
if (biometricSupportStatus != BiometricSupportStatus.NOT_SUPPORTED ||
!state.removeUnlockWithPinPolicyEnabled ||
state.isUnlockWithPinEnabled
) {
Spacer(Modifier.height(16.dp))
if (!state.isManageDevicesEnabled) {
Spacer(Modifier.height(16.dp))
}
BitwardenListHeaderText(
label = stringResource(id = BitwardenString.unlock_options),
modifier = Modifier
Expand Down Expand Up @@ -335,12 +342,29 @@ fun AccountSecurityScreen(
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(height = 8.dp))
if (state.isManageDevicesEnabled) {
BitwardenTextRow(
text = stringResource(id = BitwardenString.manage_devices),
onClick = {
viewModel.trySendAction(AccountSecurityAction.ManageDevicesClick)
},
cardStyle = CardStyle.Top(),
modifier = Modifier
.testTag("ManageDevicesLabel")
.standardHorizontalMargin()
.fillMaxWidth(),
)
}
BitwardenTextRow(
text = stringResource(id = BitwardenString.account_fingerprint_phrase),
onClick = {
viewModel.trySendAction(AccountSecurityAction.AccountFingerprintPhraseClick)
},
cardStyle = CardStyle.Top(),
cardStyle = if (state.isManageDevicesEnabled) {
CardStyle.Middle()
} else {
CardStyle.Top()
},
modifier = Modifier
.testTag("AccountFingerprintPhraseLabel")
.standardHorizontalMargin()
Expand Down
Loading
Loading