Rework credential manager flow
This commit is contained in:
parent
a3b53d6fa6
commit
4333ad764b
24 changed files with 566 additions and 148 deletions
|
@ -32,11 +32,13 @@ import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login.LoginS
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreen
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreen
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreenViewModel
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreenViewModel
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.home.presentation.overview.OverviewScreen
|
import ing.bikeshedengineer.debtpirate.app.screen.home.presentation.overview.OverviewScreen
|
||||||
|
import ing.bikeshedengineer.debtpirate.domain.credential.GetCredentialsUseCase
|
||||||
|
import ing.bikeshedengineer.debtpirate.domain.credential.StoreCredentialsUseCase
|
||||||
|
import ing.bikeshedengineer.debtpirate.domain.credential.UserCredentialManager
|
||||||
import ing.bikeshedengineer.debtpirate.domain.navigation.Destination
|
import ing.bikeshedengineer.debtpirate.domain.navigation.Destination
|
||||||
import ing.bikeshedengineer.debtpirate.domain.navigation.NavigationAction
|
import ing.bikeshedengineer.debtpirate.domain.navigation.NavigationAction
|
||||||
import ing.bikeshedengineer.debtpirate.domain.navigation.Navigator
|
import ing.bikeshedengineer.debtpirate.domain.navigation.Navigator
|
||||||
import ing.bikeshedengineer.debtpirate.domain.usecase.GetStoredTokensUseCase
|
import ing.bikeshedengineer.debtpirate.domain.usecase.GetStoredTokensUseCase
|
||||||
import ing.bikeshedengineer.debtpirate.domain.usecase.StoreCredentialsUseCase
|
|
||||||
import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme
|
import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -52,6 +54,12 @@ class MainActivity : ComponentActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var getStoredTokens: GetStoredTokensUseCase
|
lateinit var getStoredTokens: GetStoredTokensUseCase
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var userCredentialManager: UserCredentialManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var getCredentials: GetCredentialsUseCase
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var storeCredentials: StoreCredentialsUseCase
|
lateinit var storeCredentials: StoreCredentialsUseCase
|
||||||
|
|
||||||
|
@ -64,8 +72,21 @@ class MainActivity : ComponentActivity() {
|
||||||
setContent {
|
setContent {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
|
ObserveAsEvents(userCredentialManager.requestUserCredentials) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val result = getCredentials()
|
||||||
|
userCredentialManager.returnUserCredentialsResponse(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ObserveAsEvents(navigator.navigationActions) { action ->
|
ObserveAsEvents(userCredentialManager.storeUserCredentials) { credentials ->
|
||||||
|
val (emailAddress, password) = credentials
|
||||||
|
lifecycleScope.launch {
|
||||||
|
storeCredentials(emailAddress, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ObserveAsEvents(navigator.actions) { action ->
|
||||||
when (action) {
|
when (action) {
|
||||||
is NavigationAction.Navigate -> {
|
is NavigationAction.Navigate -> {
|
||||||
navController.navigate(action.destination) {
|
navController.navigate(action.destination) {
|
||||||
|
@ -108,15 +129,17 @@ class MainActivity : ComponentActivity() {
|
||||||
composable<Destination.AuthLogin> {
|
composable<Destination.AuthLogin> {
|
||||||
val viewModel = hiltViewModel<LoginScreenViewModel>()
|
val viewModel = hiltViewModel<LoginScreenViewModel>()
|
||||||
val toastMessages = viewModel.toastMessages.collectAsState("")
|
val toastMessages = viewModel.toastMessages.collectAsState("")
|
||||||
val storeCredentialMessages = viewModel.storeCredentialsMessages.collectAsState(null);
|
val storeCredentialMessages =
|
||||||
|
viewModel.storeCredentialsMessages.collectAsState(null)
|
||||||
|
|
||||||
LoginScreen(
|
LoginScreen(
|
||||||
emailAddress = viewModel.emailAddress.collectAsState(""),
|
emailAddress = viewModel.emailAddress.collectAsState(""),
|
||||||
isEmailAddressValid = viewModel.isEmailAddressValid.collectAsState(true),
|
isEmailAddressValid = viewModel.isEmailAddressValid.collectAsState(
|
||||||
|
true
|
||||||
|
),
|
||||||
password = viewModel.password.collectAsState(""),
|
password = viewModel.password.collectAsState(""),
|
||||||
isPasswordValid = viewModel.isPasswordValid.collectAsState(true),
|
isPasswordValid = viewModel.isPasswordValid.collectAsState(true),
|
||||||
toastMessages = toastMessages,
|
toastMessages = toastMessages,
|
||||||
handleCredentialManagerSignIn = viewModel::handleCredentialManagerSignIn,
|
|
||||||
onRegisterButtonClick = viewModel::onRegisterButtonClick,
|
onRegisterButtonClick = viewModel::onRegisterButtonClick,
|
||||||
onAction = viewModel::onAction,
|
onAction = viewModel::onAction,
|
||||||
)
|
)
|
||||||
|
@ -127,7 +150,9 @@ class MainActivity : ComponentActivity() {
|
||||||
RegistrationScreen(
|
RegistrationScreen(
|
||||||
emailAddress = viewModel.emailAddress.collectAsState(""),
|
emailAddress = viewModel.emailAddress.collectAsState(""),
|
||||||
emailAddressError = viewModel.emailAddressError.collectAsState(""),
|
emailAddressError = viewModel.emailAddressError.collectAsState(""),
|
||||||
isEmailAddressValid = viewModel.isEmailAddressValid.collectAsState(true),
|
isEmailAddressValid = viewModel.isEmailAddressValid.collectAsState(
|
||||||
|
true
|
||||||
|
),
|
||||||
name = viewModel.name.collectAsState(""),
|
name = viewModel.name.collectAsState(""),
|
||||||
nameError = viewModel.nameError.collectAsState(""),
|
nameError = viewModel.nameError.collectAsState(""),
|
||||||
isNameValid = viewModel.isNameValid.collectAsState(true),
|
isNameValid = viewModel.isNameValid.collectAsState(true),
|
||||||
|
@ -135,10 +160,16 @@ class MainActivity : ComponentActivity() {
|
||||||
passwordError = viewModel.passwordError.collectAsState(""),
|
passwordError = viewModel.passwordError.collectAsState(""),
|
||||||
isPasswordValid = viewModel.isPasswordValid.collectAsState(true),
|
isPasswordValid = viewModel.isPasswordValid.collectAsState(true),
|
||||||
confirmPassword = viewModel.confirmPassword.collectAsState(""),
|
confirmPassword = viewModel.confirmPassword.collectAsState(""),
|
||||||
confirmPasswordError = viewModel.confirmPasswordError.collectAsState(""),
|
confirmPasswordError = viewModel.confirmPasswordError.collectAsState(
|
||||||
isConfirmPasswordValid = viewModel.isConfirmPasswordValid.collectAsState(true),
|
""
|
||||||
|
),
|
||||||
|
isConfirmPasswordValid = viewModel.isConfirmPasswordValid.collectAsState(
|
||||||
|
true
|
||||||
|
),
|
||||||
onAction = viewModel::onAction,
|
onAction = viewModel::onAction,
|
||||||
onRegistrationMessage = viewModel.onRegistrationComplete.collectAsState(null),
|
onRegistrationMessage = viewModel.onRegistrationComplete.collectAsState(
|
||||||
|
null
|
||||||
|
),
|
||||||
navigateToConfirmationScreen = viewModel::navigateToConfirmationScreen,
|
navigateToConfirmationScreen = viewModel::navigateToConfirmationScreen,
|
||||||
onNavigateUp = viewModel::navigateUp
|
onNavigateUp = viewModel::navigateUp
|
||||||
)
|
)
|
||||||
|
@ -151,7 +182,12 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
composable<Destination.AuthUserConfirmation> {
|
composable<Destination.AuthUserConfirmation> {
|
||||||
val (userId, verificationToken) = it.toRoute<Destination.AuthUserConfirmation>()
|
val (userId, verificationToken) = it.toRoute<Destination.AuthUserConfirmation>()
|
||||||
ConfirmationScreen(ConfirmationData.UserConfirmationData(userId, verificationToken))
|
ConfirmationScreen(
|
||||||
|
ConfirmationData.UserConfirmationData(
|
||||||
|
userId,
|
||||||
|
verificationToken
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.LocalActivity
|
import androidx.activity.compose.LocalActivity
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
@ -40,12 +39,6 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.credentials.CredentialManager
|
|
||||||
import androidx.credentials.GetCredentialRequest
|
|
||||||
import androidx.credentials.GetCredentialResponse
|
|
||||||
import androidx.credentials.GetPasswordOption
|
|
||||||
import androidx.credentials.exceptions.GetCredentialException
|
|
||||||
import androidx.credentials.exceptions.NoCredentialException
|
|
||||||
|
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -55,35 +48,10 @@ fun LoginScreen(
|
||||||
password: State<String>,
|
password: State<String>,
|
||||||
isPasswordValid: State<Boolean>,
|
isPasswordValid: State<Boolean>,
|
||||||
toastMessages: State<String>,
|
toastMessages: State<String>,
|
||||||
handleCredentialManagerSignIn: (GetCredentialResponse) -> Unit,
|
|
||||||
onRegisterButtonClick: () -> Unit,
|
onRegisterButtonClick: () -> Unit,
|
||||||
onAction: (LoginScreenStateAction) -> Unit,
|
onAction: (LoginScreenStateAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalActivity.current!!
|
val context = LocalActivity.current!!
|
||||||
val credentialManager = CredentialManager.create(context)
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
try {
|
|
||||||
val result = credentialManager.getCredential(
|
|
||||||
context, GetCredentialRequest(
|
|
||||||
listOf(GetPasswordOption())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
handleCredentialManagerSignIn(result)
|
|
||||||
} catch (err: GetCredentialException) {
|
|
||||||
when (err) {
|
|
||||||
is NoCredentialException -> {
|
|
||||||
Log.i("LoginScreen", "No credentials stored")
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
Log.e("LoginScreen", "Exception thrown when getting credentials: $err")
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(toastMessages.value) {
|
LaunchedEffect(toastMessages.value) {
|
||||||
val message = toastMessages.value
|
val message = toastMessages.value
|
||||||
|
|
||||||
|
|
|
@ -5,4 +5,8 @@ sealed interface LoginScreenStateAction {
|
||||||
data class UpdatePassword(val password: String) : LoginScreenStateAction
|
data class UpdatePassword(val password: String) : LoginScreenStateAction
|
||||||
data object ValidateCredentials : LoginScreenStateAction
|
data object ValidateCredentials : LoginScreenStateAction
|
||||||
data object SubmitLoginRequest : LoginScreenStateAction
|
data object SubmitLoginRequest : LoginScreenStateAction
|
||||||
|
data class SubmitLoginRequestWithStoredCredentials(
|
||||||
|
val emailAddress: String,
|
||||||
|
val password: String
|
||||||
|
) : LoginScreenStateAction
|
||||||
}
|
}
|
|
@ -2,8 +2,6 @@ package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import androidx.credentials.GetCredentialResponse
|
|
||||||
import androidx.credentials.PasswordCredential
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
@ -12,11 +10,14 @@ import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.LoginCredentialsV
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.SubmitLoginCredentialsUseCase
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.SubmitLoginCredentialsUseCase
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.UserNotFoundException
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.UserNotFoundException
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.ValidateLoginCredentialsUseCase
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.ValidateLoginCredentialsUseCase
|
||||||
|
import ing.bikeshedengineer.debtpirate.domain.credential.UserCredentialManager
|
||||||
|
import ing.bikeshedengineer.debtpirate.domain.credential.UserCredentialResponse
|
||||||
import ing.bikeshedengineer.debtpirate.domain.navigation.Destination
|
import ing.bikeshedengineer.debtpirate.domain.navigation.Destination
|
||||||
import ing.bikeshedengineer.debtpirate.domain.navigation.Navigator
|
import ing.bikeshedengineer.debtpirate.domain.navigation.Navigator
|
||||||
import ing.bikeshedengineer.debtpirate.domain.usecase.UpdateStoreDataUseCase
|
import ing.bikeshedengineer.debtpirate.domain.usecase.UpdateStoreDataUseCase
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
@ -29,6 +30,7 @@ import javax.inject.Inject
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class LoginScreenViewModel @Inject constructor(
|
class LoginScreenViewModel @Inject constructor(
|
||||||
private val navigator: Navigator,
|
private val navigator: Navigator,
|
||||||
|
private val userCredentialManager: UserCredentialManager,
|
||||||
private val submitLoginCredentials: SubmitLoginCredentialsUseCase,
|
private val submitLoginCredentials: SubmitLoginCredentialsUseCase,
|
||||||
private val validateLoginCredentials: ValidateLoginCredentialsUseCase,
|
private val validateLoginCredentials: ValidateLoginCredentialsUseCase,
|
||||||
private val updateStoreData: UpdateStoreDataUseCase
|
private val updateStoreData: UpdateStoreDataUseCase
|
||||||
|
@ -68,8 +70,32 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
userCredentialManager.makeUserCredentialsRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
userCredentialManager.userCredentialsResponse.distinctUntilChanged()
|
||||||
|
.onEach { response ->
|
||||||
|
when (response) {
|
||||||
|
is UserCredentialResponse.HasCredentials -> {
|
||||||
|
val (emailAddress, password) = response
|
||||||
|
onAction(
|
||||||
|
LoginScreenStateAction.SubmitLoginRequestWithStoredCredentials(
|
||||||
|
emailAddress,
|
||||||
|
password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is UserCredentialResponse.NoCredentials -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_state.distinctUntilChangedBy { it.emailAddress }
|
_state.distinctUntilChangedBy { it.emailAddress }
|
||||||
.map { it.emailAddress.isNotBlank() && Patterns.EMAIL_ADDRESS.matcher(it.emailAddress).matches() }
|
.map {
|
||||||
|
it.emailAddress.isNotBlank() && Patterns.EMAIL_ADDRESS.matcher(it.emailAddress)
|
||||||
|
.matches()
|
||||||
|
}
|
||||||
.onEach { isEmailAddressValid ->
|
.onEach { isEmailAddressValid ->
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
@ -112,14 +138,17 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
onSubmitLoginRequest()
|
onSubmitLoginRequest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is LoginScreenStateAction.SubmitLoginRequestWithStoredCredentials -> {
|
||||||
|
onValidateLoginCredentials(action.emailAddress, action.password)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onValidateLoginCredentials(emailAddress: String, password: String) {
|
private fun onValidateLoginCredentials(emailAddress: String, password: String) {
|
||||||
_state.update { it.copy(isEmailAddressPristine = true, isPasswordPristine = true) }
|
_state.update { it.copy(isEmailAddressPristine = true, isPasswordPristine = true) }
|
||||||
|
|
||||||
val validationResult = validateLoginCredentials(emailAddress, password)
|
when (val validationResult = validateLoginCredentials(emailAddress, password)) {
|
||||||
when (validationResult) {
|
|
||||||
is LoginCredentialsValidationResult.ValidCredentials -> {
|
is LoginCredentialsValidationResult.ValidCredentials -> {
|
||||||
onAction(LoginScreenStateAction.SubmitLoginRequest)
|
onAction(LoginScreenStateAction.SubmitLoginRequest)
|
||||||
}
|
}
|
||||||
|
@ -133,7 +162,11 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
|
|
||||||
private suspend fun onSubmitLoginRequest() {
|
private suspend fun onSubmitLoginRequest() {
|
||||||
try {
|
try {
|
||||||
val (userId, auth, session) = submitLoginCredentials(_state.value.emailAddress, _state.value.password)
|
val (userId, auth, session) = submitLoginCredentials(
|
||||||
|
_state.value.emailAddress,
|
||||||
|
_state.value.password
|
||||||
|
)
|
||||||
|
|
||||||
updateStoreData(
|
updateStoreData(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
authToken = auth.token,
|
authToken = auth.token,
|
||||||
|
@ -141,6 +174,11 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
sessionToken = session.token,
|
sessionToken = session.token,
|
||||||
sessionTokenExpiresAt = session.expiresAt
|
sessionTokenExpiresAt = session.expiresAt
|
||||||
)
|
)
|
||||||
|
|
||||||
|
userCredentialManager.storeUserCredentials(
|
||||||
|
_state.value.emailAddress,
|
||||||
|
_state.value.password
|
||||||
|
)
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
when (err) {
|
when (err) {
|
||||||
is InvalidCredentialsException -> {
|
is InvalidCredentialsException -> {
|
||||||
|
@ -163,20 +201,4 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
navigator.navigate(destination = Destination.AuthRegistration)
|
navigator.navigate(destination = Destination.AuthRegistration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleCredentialManagerSignIn(result: GetCredentialResponse) {
|
|
||||||
val credentials = result.credential
|
|
||||||
when (credentials) {
|
|
||||||
is PasswordCredential -> {
|
|
||||||
val emailAddress = credentials.id
|
|
||||||
val password = credentials.password
|
|
||||||
|
|
||||||
onAction(LoginScreenStateAction.SubmitLoginRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
// TODO: Handle this...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,42 +0,0 @@
|
||||||
package ing.bikeshedengineer.debtpirate.domain.credential
|
|
||||||
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
object CredentialManagerModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideCredentialManager(): CredentialManager = CredentialManagerImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface CredentialManagerRequest {
|
|
||||||
data object GetCredentials : CredentialManagerRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface CredentialManagerResponse {
|
|
||||||
data object NoCredentials : CredentialManagerResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CredentialManager {
|
|
||||||
val credentialRequests: Flow<CredentialManagerRequest>
|
|
||||||
|
|
||||||
suspend fun getCredentials()
|
|
||||||
}
|
|
||||||
|
|
||||||
class CredentialManagerImpl() : CredentialManager {
|
|
||||||
private val _credentialRequests = Channel<CredentialManagerRequest>()
|
|
||||||
override val credentialRequests = _credentialRequests.receiveAsFlow()
|
|
||||||
|
|
||||||
override suspend fun getCredentials() {
|
|
||||||
this._credentialRequests.send(CredentialManagerRequest.GetCredentials)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.domain.credential
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.components.ActivityComponent
|
||||||
|
import dagger.hilt.android.scopes.ActivityScoped
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(ActivityComponent::class)
|
||||||
|
object GetCredentialsUseCaseModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ActivityScoped
|
||||||
|
fun provideGetCredentialsUseCase(repository: UserCredentialRepository) =
|
||||||
|
GetCredentialsUseCase(repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetCredentialsUseCase(private val repository: UserCredentialRepository) {
|
||||||
|
suspend operator fun invoke(): UserCredentialResponse {
|
||||||
|
return repository.getUserCredentials()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.domain.credential
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.components.ActivityComponent
|
||||||
|
import dagger.hilt.android.scopes.ActivityScoped
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(ActivityComponent::class)
|
||||||
|
object StoreCredentialsUseCaseModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ActivityScoped
|
||||||
|
fun provideStoreCredentialsUseCase(repository: UserCredentialRepository) =
|
||||||
|
StoreCredentialsUseCase(repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
class StoreCredentialsUseCase(private val repository: UserCredentialRepository) {
|
||||||
|
suspend operator fun invoke(emailAddress: String, password: String) {
|
||||||
|
this.repository.storeUserCredentials(emailAddress, password)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.domain.credential
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object UserCredentialManagerModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideUserCredentialManager(): UserCredentialManager = UserCredentialManagerImpl()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserCredentialManager {
|
||||||
|
val requestUserCredentials: Flow<Unit>
|
||||||
|
suspend fun makeUserCredentialsRequest()
|
||||||
|
|
||||||
|
val userCredentialsResponse: Flow<UserCredentialResponse>
|
||||||
|
suspend fun returnUserCredentialsResponse(response: UserCredentialResponse)
|
||||||
|
|
||||||
|
val storeUserCredentials: Flow<Pair<String, String>>
|
||||||
|
suspend fun storeUserCredentials(emailAddress: String, password: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserCredentialManagerImpl : UserCredentialManager {
|
||||||
|
private val _requestUserCredentials = Channel<Unit>()
|
||||||
|
override val requestUserCredentials = _requestUserCredentials.receiveAsFlow()
|
||||||
|
|
||||||
|
private val _userCredentialsResponse = Channel<UserCredentialResponse>()
|
||||||
|
override val userCredentialsResponse = _userCredentialsResponse.receiveAsFlow()
|
||||||
|
|
||||||
|
private val _storeUserCredentials = Channel<Pair<String, String>>()
|
||||||
|
override val storeUserCredentials = _storeUserCredentials.receiveAsFlow()
|
||||||
|
|
||||||
|
override suspend fun makeUserCredentialsRequest() {
|
||||||
|
_requestUserCredentials.send(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun returnUserCredentialsResponse(response: UserCredentialResponse) {
|
||||||
|
_userCredentialsResponse.send(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun storeUserCredentials(emailAddress: String, password: String) {
|
||||||
|
_storeUserCredentials.send(Pair(emailAddress, password))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.domain.credential
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.credentials.CreatePasswordRequest
|
||||||
|
import androidx.credentials.CredentialManager
|
||||||
|
import androidx.credentials.GetCredentialRequest
|
||||||
|
import androidx.credentials.GetPasswordOption
|
||||||
|
import androidx.credentials.PasswordCredential
|
||||||
|
import androidx.credentials.exceptions.CreateCredentialException
|
||||||
|
import androidx.credentials.exceptions.GetCredentialException
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.components.ActivityComponent
|
||||||
|
import dagger.hilt.android.qualifiers.ActivityContext
|
||||||
|
import dagger.hilt.android.scopes.ActivityScoped
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(ActivityComponent::class)
|
||||||
|
object UserCredentialModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ActivityScoped
|
||||||
|
fun provideUserCredentialRepository(@ActivityContext context: Context): UserCredentialRepository =
|
||||||
|
UserCredentialRepositoryImpl(context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserCredentialRepository {
|
||||||
|
suspend fun getUserCredentials(): UserCredentialResponse
|
||||||
|
suspend fun storeUserCredentials(emailAddress: String, password: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserCredentialRepositoryImpl(
|
||||||
|
private val context: Context,
|
||||||
|
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||||
|
) : UserCredentialRepository {
|
||||||
|
private val credentialManager = CredentialManager.create(this.context)
|
||||||
|
|
||||||
|
override suspend fun getUserCredentials(): UserCredentialResponse {
|
||||||
|
val getCredentialRequest = GetCredentialRequest(listOf(GetPasswordOption()))
|
||||||
|
return try {
|
||||||
|
val result = withContext(this.defaultDispatcher) {
|
||||||
|
credentialManager.getCredential(
|
||||||
|
context,
|
||||||
|
getCredentialRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val credential = result.credential as PasswordCredential
|
||||||
|
UserCredentialResponse.HasCredentials(
|
||||||
|
emailAddress = credential.id,
|
||||||
|
password = credential.password
|
||||||
|
)
|
||||||
|
} catch (err: GetCredentialException) {
|
||||||
|
UserCredentialResponse.NoCredentials
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun storeUserCredentials(emailAddress: String, password: String) {
|
||||||
|
val storeCredentialsRequest = CreatePasswordRequest(id = emailAddress, password = password)
|
||||||
|
try {
|
||||||
|
withContext(this.defaultDispatcher) {
|
||||||
|
credentialManager.createCredential(context, storeCredentialsRequest)
|
||||||
|
}
|
||||||
|
} catch (err: CreateCredentialException) {
|
||||||
|
Log.e(
|
||||||
|
"UserCredentialRepository",
|
||||||
|
"Unable to store user credentials in the credential manager: $err"
|
||||||
|
)
|
||||||
|
// TODO: Do something with this error...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.domain.credential
|
||||||
|
|
||||||
|
sealed interface UserCredentialResponse {
|
||||||
|
data object NoCredentials : UserCredentialResponse
|
||||||
|
data class HasCredentials(val emailAddress: String, val password: String) :
|
||||||
|
UserCredentialResponse
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
|
||||||
interface Navigator {
|
interface Navigator {
|
||||||
val startDestination: Destination
|
val startDestination: Destination
|
||||||
val navigationActions: Flow<NavigationAction>
|
val actions: Flow<NavigationAction>
|
||||||
|
|
||||||
suspend fun navigate(destination: Destination, navOptions: NavOptionsBuilder.() -> Unit = {})
|
suspend fun navigate(destination: Destination, navOptions: NavOptionsBuilder.() -> Unit = {})
|
||||||
|
|
||||||
|
@ -17,17 +17,17 @@ interface Navigator {
|
||||||
class NavigatorImpl(
|
class NavigatorImpl(
|
||||||
override val startDestination: Destination = Destination.AuthGraph
|
override val startDestination: Destination = Destination.AuthGraph
|
||||||
) : Navigator {
|
) : Navigator {
|
||||||
private val _navigationActions = Channel<NavigationAction>()
|
private val _actions = Channel<NavigationAction>()
|
||||||
override val navigationActions = _navigationActions.receiveAsFlow()
|
override val actions = _actions.receiveAsFlow()
|
||||||
|
|
||||||
override suspend fun navigate(
|
override suspend fun navigate(
|
||||||
destination: Destination,
|
destination: Destination,
|
||||||
navOptions: NavOptionsBuilder.() -> Unit
|
navOptions: NavOptionsBuilder.() -> Unit
|
||||||
) {
|
) {
|
||||||
_navigationActions.send(NavigationAction.Navigate(destination, navOptions))
|
_actions.send(NavigationAction.Navigate(destination, navOptions))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun navigateUp() {
|
override suspend fun navigateUp() {
|
||||||
_navigationActions.send(NavigationAction.NavigateUp)
|
_actions.send(NavigationAction.NavigateUp)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
package ing.bikeshedengineer.debtpirate.domain.usecase
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.credentials.CreatePasswordRequest
|
|
||||||
import androidx.credentials.CredentialManager
|
|
||||||
import androidx.credentials.exceptions.CreateCredentialException
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.components.ActivityComponent
|
|
||||||
import dagger.hilt.android.qualifiers.ActivityContext
|
|
||||||
import dagger.hilt.android.scopes.ActivityScoped
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(ActivityComponent::class)
|
|
||||||
object StoreCredentialsUseCaseModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@ActivityScoped
|
|
||||||
fun provideStoreCredentialsUseCase(@ActivityContext context: Context) = StoreCredentialsUseCase(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
class StoreCredentialsUseCase(val context: Context) {
|
|
||||||
suspend operator fun invoke(username: String, password: String) {
|
|
||||||
val credentialManager = CredentialManager.create(this.context)
|
|
||||||
val createPasswordRequest = CreatePasswordRequest(id = username, password = password)
|
|
||||||
|
|
||||||
try {
|
|
||||||
credentialManager.createCredential(this.context, createPasswordRequest)
|
|
||||||
Log.d("StoreCredentialsUseCase", "Successfully stored login credentials")
|
|
||||||
} catch (err: CreateCredentialException) {
|
|
||||||
// TODO: Throw an error to be displayed as a Toast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
[versions]
|
[versions]
|
||||||
activityCompose = "1.10.1"
|
activityCompose = "1.10.1"
|
||||||
agp = "8.9.0"
|
agp = "8.9.1"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
biometric = "1.4.0-alpha02"
|
biometric = "1.4.0-alpha02"
|
||||||
composeBom = "2025.03.00"
|
composeBom = "2025.03.00"
|
||||||
|
|
29
bruno/Debt Pirate/account/create.bru
Normal file
29
bruno/Debt Pirate/account/create.bru
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
meta {
|
||||||
|
name: create
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{protocol}}//{{domain}}/account
|
||||||
|
body: json
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Accept: application/json;charset=utf-8
|
||||||
|
Content-Type: application/json;charset=utf-8
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{sessionToken}}
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"type": "asset",
|
||||||
|
"name": "Checking",
|
||||||
|
"description": "Bank 1",
|
||||||
|
"currencyCode": "USD"
|
||||||
|
}
|
||||||
|
}
|
28
bruno/Debt Pirate/account/read.bru
Normal file
28
bruno/Debt Pirate/account/read.bru
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
meta {
|
||||||
|
name: read
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{protocol}}//{{domain}}/account
|
||||||
|
body: none
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Accept: application/json;charset=utf-8
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{sessionToken}}
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"type": "asset",
|
||||||
|
"name": "Quick Cash Account",
|
||||||
|
"description": "Franklin Savings Bank",
|
||||||
|
"currencyCode": "USD"
|
||||||
|
}
|
||||||
|
}
|
41
bruno/Debt Pirate/auth/login.bru
Normal file
41
bruno/Debt Pirate/auth/login.bru
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
meta {
|
||||||
|
name: login
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{protocol}}//{{domain}}/auth/login
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Accept: application/json; charset=utf-8
|
||||||
|
Content-Type: application/json; charset=utf-8
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"email": "{{email}}",
|
||||||
|
"password": "{{password}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
script:post-response {
|
||||||
|
const body = res.getBody();
|
||||||
|
const data = body.data;
|
||||||
|
|
||||||
|
if (res.status < 300) {
|
||||||
|
bru.setEnvVar("userId", data.id);
|
||||||
|
|
||||||
|
if (data.auth?.token) {
|
||||||
|
bru.setEnvVar("authToken", data.auth.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.session?.token) {
|
||||||
|
bru.setEnvVar("sessionToken", data.session.token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
bruno/Debt Pirate/auth/session.bru
Normal file
33
bruno/Debt Pirate/auth/session.bru
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
meta {
|
||||||
|
name: session
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{protocol}}//{{domain}}/auth/session
|
||||||
|
body: none
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Accept: application/json; charset=utf-8
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{authToken}}
|
||||||
|
}
|
||||||
|
|
||||||
|
script:post-response {
|
||||||
|
const body = res.getBody();
|
||||||
|
const data = body.data;
|
||||||
|
|
||||||
|
if (res.status < 300) {
|
||||||
|
bru.setEnvVar("userId", data.id);
|
||||||
|
|
||||||
|
if (data.session?.token) {
|
||||||
|
bru.setEnvVar("sessionToken", data.token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
bruno/Debt Pirate/bruno.json
Normal file
9
bruno/Debt Pirate/bruno.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"name": "Debt Pirate",
|
||||||
|
"type": "collection",
|
||||||
|
"ignore": [
|
||||||
|
"node_modules",
|
||||||
|
".git"
|
||||||
|
]
|
||||||
|
}
|
28
bruno/Debt Pirate/budget/create.bru
Normal file
28
bruno/Debt Pirate/budget/create.bru
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
meta {
|
||||||
|
name: create
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{protocol}}//{{domain}}/budget
|
||||||
|
body: json
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Accept: application/json;charset=utf-8
|
||||||
|
Content-Type: application/json;charset=utf-8
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{sessionToken}}
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"name": "Daily Living",
|
||||||
|
"description": "Daily living expenses",
|
||||||
|
"icon": "person"
|
||||||
|
}
|
||||||
|
}
|
28
bruno/Debt Pirate/budget/read.bru
Normal file
28
bruno/Debt Pirate/budget/read.bru
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
meta {
|
||||||
|
name: read
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{protocol}}//{{domain}}/budget
|
||||||
|
body: none
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Accept: application/json;charset=utf-8
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{sessionToken}}
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"type": "asset",
|
||||||
|
"name": "Quick Cash Account",
|
||||||
|
"description": "Franklin Savings Bank",
|
||||||
|
"currencyCode": "USD"
|
||||||
|
}
|
||||||
|
}
|
0
bruno/Debt Pirate/collection.bru
Normal file
0
bruno/Debt Pirate/collection.bru
Normal file
11
bruno/Debt Pirate/environments/Localhost.bru
Normal file
11
bruno/Debt Pirate/environments/Localhost.bru
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
vars {
|
||||||
|
domain: localhost:42069
|
||||||
|
protocol: http:
|
||||||
|
email: zachary@dziura.email
|
||||||
|
}
|
||||||
|
vars:secret [
|
||||||
|
password,
|
||||||
|
verificationToken,
|
||||||
|
sessionToken,
|
||||||
|
authToken
|
||||||
|
]
|
34
bruno/Debt Pirate/user/create.bru
Normal file
34
bruno/Debt Pirate/user/create.bru
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
meta {
|
||||||
|
name: create
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{protocol}}//{{domain}}/user
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json; charset=utf-8
|
||||||
|
Accept: application/json; charset=utf-8
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"email": "{{email}}",
|
||||||
|
"password": "{{password}}",
|
||||||
|
"name": "Z. Charles Dziura"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
script:post-response {
|
||||||
|
const body = res.getBody();
|
||||||
|
const data = body.data;
|
||||||
|
|
||||||
|
if (res.status < 300 && data.sessionToken) {
|
||||||
|
bru.setEnvVar("verificationToken", data.sessionToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
42
bruno/Debt Pirate/user/verify.bru
Normal file
42
bruno/Debt Pirate/user/verify.bru
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
meta {
|
||||||
|
name: verify
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{protocol}}//{{domain}}/user/verify?t={{verificationToken}}
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
params:query {
|
||||||
|
t: {{verificationToken}}
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"username": "{{username}}",
|
||||||
|
"password": "{{password}}",
|
||||||
|
"email": "zachary@dziura.email",
|
||||||
|
"name": "Z. Charles Dziura"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
script:post-response {
|
||||||
|
const body = res.getBody();
|
||||||
|
const data = body.data;
|
||||||
|
|
||||||
|
if (res.status < 300) {
|
||||||
|
bru.setEnvVar("userId", data.userId);
|
||||||
|
|
||||||
|
if (data.auth?.token) {
|
||||||
|
bru.setEnvVar("authToken", data.auth.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.session?.token) {
|
||||||
|
bru.setEnvVar("sessionToken", data.session.token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue