mirror of
https://github.com/bitwarden/android.git
synced 2026-05-13 07:11:10 -05:00
[Pm-6702] Pass arguments from AppLink to Complete registration. Compute region from url domain
This commit is contained in:
@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.util
|
||||
|
||||
import android.content.Intent
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
||||
/**
|
||||
* Checks if the given [Intent] contains data to complete registration.
|
||||
@@ -9,11 +10,16 @@ import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
||||
*/
|
||||
fun Intent.getCompleteRegistrationDataIntentOrNull(): CompleteRegistrationData? {
|
||||
val uri = data ?: return null
|
||||
val host = uri.host ?: return null
|
||||
val email = uri?.getQueryParameter("email") ?: return null
|
||||
val verificationToken = uri.getQueryParameter("verificationtoken") ?: return null
|
||||
if (!host.contains("bitwarden.eu") && !host.contains("bitwarden.com")) return null
|
||||
|
||||
val region = if (host.contains("bitwarden.eu")) Environment.Type.EU else Environment.Type.US
|
||||
return CompleteRegistrationData(
|
||||
email = email,
|
||||
verificationToken = verificationToken
|
||||
verificationToken = verificationToken,
|
||||
region = region
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
@@ -13,4 +14,5 @@ import kotlinx.parcelize.Parcelize
|
||||
data class CompleteRegistrationData(
|
||||
val email: String,
|
||||
val verificationToken: String,
|
||||
val region: Environment.Type
|
||||
) : Parcelable
|
||||
|
||||
@@ -58,8 +58,9 @@ fun NavGraphBuilder.authGraph(navController: NavHostController) {
|
||||
startRegistrationDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
// TODO check necessary parameters
|
||||
onNavigateToCompleteRegistration = { emailAddress, verificationToken, captchaToken ->
|
||||
navController.navigateToCompleteRegistration()
|
||||
onNavigateToCompleteRegistration = { emailAddress, verificationToken ->
|
||||
navController.navigateToCompleteRegistration(emailAddress= emailAddress,
|
||||
verificationToken = verificationToken )
|
||||
},
|
||||
onNavigateToCheckEmail = {emailAddress ->
|
||||
navController.navigateToCheckEmail(emailAddress)
|
||||
|
||||
@@ -4,16 +4,43 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
|
||||
private const val COMPLETE_REGISTRATION_ROUTE = "complete_registration"
|
||||
private const val EMAIL_ADDRESS: String = "email_address"
|
||||
private const val VERIFICATION_TOKEN: String = "verification_token"
|
||||
private const val REGION: String = "region"
|
||||
private const val COMPLETE_REGISTRATION_PREFIX = "complete_registration"
|
||||
private const val COMPLETE_REGISTRATION_ROUTE = "$COMPLETE_REGISTRATION_PREFIX/{$EMAIL_ADDRESS}/{$VERIFICATION_TOKEN}/{$REGION}"
|
||||
|
||||
/**
|
||||
* Class to retrieve login with device arguments from the [SavedStateHandle].
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
data class CompleteRegistrationArgs(
|
||||
val emailAddress: String,
|
||||
val verificationToken: String,
|
||||
val region: Environment.Type
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
emailAddress = checkNotNull(savedStateHandle.get<String>(EMAIL_ADDRESS)),
|
||||
verificationToken = checkNotNull(savedStateHandle.get<String>(VERIFICATION_TOKEN)),
|
||||
region = checkNotNull(savedStateHandle.get<Environment.Type>(REGION))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the complete registration screen.
|
||||
*/
|
||||
fun NavController.navigateToCompleteRegistration(navOptions: NavOptions? = null) {
|
||||
this.navigate(COMPLETE_REGISTRATION_ROUTE, navOptions)
|
||||
fun NavController.navigateToCompleteRegistration(
|
||||
emailAddress: String,
|
||||
verificationToken: String,
|
||||
region: Environment.Type? = null,
|
||||
navOptions: NavOptions? = null) {
|
||||
this.navigate("$COMPLETE_REGISTRATION_PREFIX/$emailAddress/$verificationToken/$region", navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,6 +52,11 @@ fun NavGraphBuilder.completeRegistrationDestination(
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = COMPLETE_REGISTRATION_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(EMAIL_ADDRESS) { type = NavType.StringType },
|
||||
navArgument(VERIFICATION_TOKEN) { type = NavType.StringType },
|
||||
navArgument(REGION) { type = NavType.EnumType(Environment.Type::class.java) },
|
||||
),
|
||||
) {
|
||||
CompleteRegistrationScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
|
||||
@@ -73,7 +73,7 @@ fun CompleteRegistrationScreen(
|
||||
when (event) {
|
||||
is CompleteRegistrationEvent.NavigateBack -> onNavigateBack.invoke()
|
||||
is CompleteRegistrationEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.text, Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, event.message(context.resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
is CompleteRegistrationEvent.NavigateToCaptcha -> {
|
||||
|
||||
@@ -15,18 +15,22 @@ import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.CheckDataBreachesToggle
|
||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.ConfirmPasswordInputChange
|
||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.ContinueWithBreachedPasswordClick
|
||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.Internal.ReceivePasswordStrengthResult
|
||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.PasswordHintChange
|
||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.PasswordInputChange
|
||||
import com.x8bit.bitwarden.ui.auth.feature.landing.LandingEvent
|
||||
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.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.isValidEmail
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.SendEvent
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -47,19 +51,24 @@ private const val MIN_PASSWORD_LENGTH = 12
|
||||
class CompleteRegistrationViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val authRepository: AuthRepository,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
private val specialCircumstance: SpecialCircumstanceManager
|
||||
) : BaseViewModel<CompleteRegistrationState, CompleteRegistrationEvent, CompleteRegistrationAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: CompleteRegistrationState(
|
||||
userEmail = "",
|
||||
emailVerificationToken = "",
|
||||
passwordInput = "",
|
||||
confirmPasswordInput = "",
|
||||
passwordHintInput = "",
|
||||
isCheckDataBreachesToggled = true,
|
||||
dialog = null,
|
||||
passwordStrengthState = PasswordStrengthState.NONE,
|
||||
),
|
||||
?: run {
|
||||
val args = CompleteRegistrationArgs(savedStateHandle)
|
||||
CompleteRegistrationState(
|
||||
userEmail = args.emailAddress,
|
||||
emailVerificationToken = args.verificationToken,
|
||||
region = args.region,
|
||||
passwordInput = "",
|
||||
confirmPasswordInput = "",
|
||||
passwordHintInput = "",
|
||||
isCheckDataBreachesToggled = true,
|
||||
dialog = null,
|
||||
passwordStrengthState = PasswordStrengthState.NONE,
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
||||
/**
|
||||
@@ -69,6 +78,8 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||
private var passwordStrengthJob: Job = Job().apply { complete() }
|
||||
|
||||
init {
|
||||
updateCurrentRegion()
|
||||
verifyEmailAddress()
|
||||
// As state updates, write to saved state handle:
|
||||
stateFlow
|
||||
.onEach { savedStateHandle[KEY_STATE] = it }
|
||||
@@ -85,6 +96,38 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
// Update region when coming from an AppLink to complete the registration
|
||||
private fun updateCurrentRegion() {
|
||||
if (state.region == null) {
|
||||
return
|
||||
}
|
||||
val environment = when (state.region) {
|
||||
Environment.Type.US -> Environment.Us
|
||||
Environment.Type.EU -> Environment.Eu
|
||||
Environment.Type.SELF_HOSTED -> {
|
||||
// Self-host does not have email confirmation
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Update the environment in the repo; the VM state will update accordingly because it is
|
||||
// listening for changes.
|
||||
environmentRepository.environment = environment
|
||||
}
|
||||
|
||||
private fun verifyEmailAddress() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = CompleteRegistrationDialog.Loading)
|
||||
}
|
||||
|
||||
// TODO Add call to service to verify email
|
||||
sendEvent(CompleteRegistrationEvent.ShowToast(message = R.string.email_verified.asText()))
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
// clean the specialCircumstance after being handled
|
||||
specialCircumstance.specialCircumstance = null
|
||||
@@ -365,6 +408,7 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||
data class CompleteRegistrationState(
|
||||
val userEmail: String,
|
||||
val emailVerificationToken: String,
|
||||
val region: Environment.Type,
|
||||
val passwordInput: String,
|
||||
val confirmPasswordInput: String,
|
||||
val passwordHintInput: String,
|
||||
@@ -442,9 +486,11 @@ sealed class CompleteRegistrationEvent {
|
||||
data object NavigateBack : CompleteRegistrationEvent()
|
||||
|
||||
/**
|
||||
* Placeholder event for showing a toast. Can be removed once there are real events.
|
||||
* Show a toast with the given message.
|
||||
*/
|
||||
data class ShowToast(val text: String) : CompleteRegistrationEvent()
|
||||
data class ShowToast(
|
||||
val message: Text,
|
||||
) : CompleteRegistrationEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the captcha verification screen.
|
||||
|
||||
@@ -21,8 +21,7 @@ fun NavGraphBuilder.startRegistrationDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToCompleteRegistration: (
|
||||
emailAddress: String,
|
||||
verificationToken: String,
|
||||
captchaToken: String
|
||||
verificationToken: String
|
||||
) -> Unit,
|
||||
onNavigateToCheckEmail: (email: String) -> Unit,
|
||||
onNavigateToEnvironment: () -> Unit,
|
||||
|
||||
@@ -79,8 +79,7 @@ fun StartRegistrationScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToCompleteRegistration: (
|
||||
emailAddress: String,
|
||||
verificationToken: String,
|
||||
captchaToken: String) -> Unit,
|
||||
verificationToken: String) -> Unit,
|
||||
onNavigateToCheckEmail: (email: String) -> Unit,
|
||||
onNavigateToEnvironment: () -> Unit,
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
@@ -114,8 +113,7 @@ fun StartRegistrationScreen(
|
||||
is StartRegistrationEvent.NavigateToCompleteRegistration -> {
|
||||
onNavigateToCompleteRegistration(
|
||||
event.email,
|
||||
event.verificationToken,
|
||||
event.captchaToken,
|
||||
event.verificationToken
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -197,7 +197,11 @@ fun RootNavScreen(
|
||||
|
||||
is RootNavState.CompleteOngoingRegistration -> {
|
||||
navController.navigateToAuthGraph(rootNavOptions)
|
||||
navController.navigateToCompleteRegistration()
|
||||
navController.navigateToCompleteRegistration(
|
||||
emailAddress = currentState.email,
|
||||
verificationToken = currentState.verificationToken,
|
||||
region = currentState.region
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -80,6 +81,7 @@ class RootNavViewModel @Inject constructor(
|
||||
RootNavState.CompleteOngoingRegistration(
|
||||
email = specialCircumstance.completeRegistrationData.email,
|
||||
verificationToken = specialCircumstance.completeRegistrationData.verificationToken,
|
||||
region = specialCircumstance.completeRegistrationData.region,
|
||||
timestamp = specialCircumstance.timestamp
|
||||
)
|
||||
}
|
||||
@@ -125,6 +127,7 @@ class RootNavViewModel @Inject constructor(
|
||||
RootNavState.CompleteOngoingRegistration (
|
||||
email = specialCircumstance.completeRegistrationData.email,
|
||||
verificationToken = specialCircumstance.completeRegistrationData.verificationToken,
|
||||
region = specialCircumstance.completeRegistrationData.region,
|
||||
timestamp = specialCircumstance.timestamp
|
||||
)
|
||||
}
|
||||
@@ -229,6 +232,7 @@ sealed class RootNavState : Parcelable {
|
||||
data class CompleteOngoingRegistration (
|
||||
val email: String,
|
||||
val verificationToken: String,
|
||||
val region: Environment.Type,
|
||||
val timestamp: Timestamp
|
||||
) : RootNavState()
|
||||
|
||||
|
||||
@@ -921,6 +921,7 @@ Do you want to switch to this account?</string>
|
||||
<string name="check_your_email">Check your email</string>
|
||||
<string name="open_email_app">Open email app</string>
|
||||
<string name="go_back">Go back</string>
|
||||
<string name="email_verified">Email verified</string>
|
||||
<string name="no_email_go_back_to_edit_your_email_address">No email? Go back to edit your email address.</string>
|
||||
<string name="or_log_in_you_may_already_have_an_account">Or log in, you may already have an account.</string>
|
||||
<string name="get_emails_from_bitwarden_for_announcements_advices_and_research_opportunities_unsubscribe_any_time">Get emails from Bitwarden for announcements, advice, and research opportunities. Unsubscribe at any time.</string>
|
||||
|
||||
Reference in New Issue
Block a user