Display a toast message when submitting invalid login credentials
This commit is contained in:
parent
836e24247e
commit
78a4b75acd
11 changed files with 49 additions and 27 deletions
|
@ -37,8 +37,8 @@ impl AppError {
|
|||
Self::new(ErrorKind::ExpiredToken)
|
||||
}
|
||||
|
||||
pub fn invalid_password() -> Self {
|
||||
Self::new(ErrorKind::InvalidPassword)
|
||||
pub fn invalid_credentials() -> Self {
|
||||
Self::new(ErrorKind::InvalidCredentials)
|
||||
}
|
||||
|
||||
pub fn invalid_token() -> Self {
|
||||
|
@ -68,6 +68,10 @@ impl AppError {
|
|||
pub fn is_duplicate_record(&self) -> bool {
|
||||
matches!(self.kind, ErrorKind::DuplicateRecord(_))
|
||||
}
|
||||
|
||||
pub fn is_no_record_found(&self) -> bool {
|
||||
matches!(self.kind, ErrorKind::NoDbRecordFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorKind> for AppError {
|
||||
|
@ -131,7 +135,7 @@ impl Display for AppError {
|
|||
write!(f, "Duplicate database record: {message}")
|
||||
}
|
||||
ErrorKind::ExpiredToken => write!(f, "The provided token has expired"),
|
||||
ErrorKind::InvalidPassword => write!(f, "Invalid password"),
|
||||
ErrorKind::InvalidCredentials => write!(f, "Invalid email address or password"),
|
||||
ErrorKind::InvalidToken => write!(f, "The provided token is invalid"),
|
||||
ErrorKind::MissingAuthorizationToken => write!(f, "Missing authorization token"),
|
||||
ErrorKind::MissingEnvironmentVariables(missing_vars) => write!(
|
||||
|
@ -164,7 +168,7 @@ enum ErrorKind {
|
|||
DbMigration(MigrateError),
|
||||
DuplicateRecord(String),
|
||||
ExpiredToken,
|
||||
InvalidPassword,
|
||||
InvalidCredentials,
|
||||
InvalidToken,
|
||||
MissingEnvironmentVariables(Vec<&'static str>),
|
||||
MissingSessionField(&'static str),
|
||||
|
@ -182,7 +186,7 @@ impl IntoResponse for AppError {
|
|||
StatusCode::CONFLICT,
|
||||
ApiResponse::new_with_error(self).into_json_response(),
|
||||
),
|
||||
&ErrorKind::InvalidPassword | &ErrorKind::InvalidToken => (
|
||||
&ErrorKind::InvalidCredentials | &ErrorKind::InvalidToken => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
ApiResponse::new_with_error(self).into_json_response(),
|
||||
),
|
||||
|
|
|
@ -48,7 +48,15 @@ async fn auth_login_request(
|
|||
let UserIdAndHashedPasswordEntity {
|
||||
id: user_id,
|
||||
password: hashed_password,
|
||||
} = get_username_and_password_by_email(db_pool, email).await?;
|
||||
} = get_username_and_password_by_email(db_pool, email)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
if err.is_no_record_found() {
|
||||
AppError::invalid_credentials()
|
||||
} else {
|
||||
err
|
||||
}
|
||||
})?;
|
||||
|
||||
verify_password(password, hashed_password)?;
|
||||
|
||||
|
|
|
@ -21,5 +21,5 @@ pub fn verify_password(password: String, hashed_password: String) -> Result<(),
|
|||
|
||||
algorithm
|
||||
.verify_password(password.as_bytes(), &hash)
|
||||
.map_err(|_| AppError::invalid_password())
|
||||
.map_err(|_| AppError::invalid_credentials())
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
|
||||
private fun handleNewUserVerificationIntent(userId: Int, verificationToken: String) {
|
||||
Log.d("DebtPirate::MainActivity", "User ID: $userId, Session Token: $verificationToken")
|
||||
Log.d("MainActivity", "User ID: $userId, Session Token: $verificationToken")
|
||||
|
||||
lifecycleScope.launch {
|
||||
navigator.navigate(Destination.AuthUserConfirmation(userId, verificationToken))
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
@ -71,6 +73,13 @@ fun LoginScreen(
|
|||
}
|
||||
}
|
||||
|
||||
val toastMessages = viewModel.toastMessages.collectAsState("")
|
||||
LaunchedEffect(toastMessages.value) {
|
||||
val message = toastMessages.value
|
||||
Log.d("LoginScreen", message)
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
@ -11,7 +13,9 @@ import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.ValidateLoginCred
|
|||
import ing.bikeshedengineer.debtpirate.domain.model.Token
|
||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
||||
import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
@ -47,6 +51,9 @@ class LoginScreenViewModel @Inject constructor(
|
|||
private val _password = MutableStateFlow("")
|
||||
val password = _password.asStateFlow()
|
||||
|
||||
private val _toastMessages = MutableSharedFlow<String>()
|
||||
val toastMessages = _toastMessages.asSharedFlow()
|
||||
|
||||
fun onAction(action: LoginScreenStateAction) {
|
||||
when (action) {
|
||||
is LoginScreenStateAction.UpdateEmailAddress -> {
|
||||
|
@ -87,8 +94,8 @@ class LoginScreenViewModel @Inject constructor(
|
|||
private suspend fun onSubmitLoginRequest(emailAddress: String, password: String) {
|
||||
try {
|
||||
val result = submitLoginCredentials(emailAddress, password)
|
||||
} catch (err: Throwable) {
|
||||
// TODO...
|
||||
} catch (err: Exception) {
|
||||
_toastMessages.emit("Invalid Email Address or Password")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
|
@ -73,7 +72,7 @@ fun RegistrationScreen(
|
|||
|
||||
if (result !is AccountManagerResult.Success) {
|
||||
Log.i(
|
||||
"DebtPirate::RegistrationScreen",
|
||||
"RegistrationScreen",
|
||||
"Not able to store credentials in CredentialManager"
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,24 +1,15 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.usecase
|
||||
|
||||
import android.util.Log
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginPostResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.repository.AuthRepository
|
||||
|
||||
|
||||
class SubmitLoginCredentialsUseCase(
|
||||
private val authRepository: AuthRepository
|
||||
) {
|
||||
suspend operator fun invoke(emailAddress: String, password: String): AuthLoginPostResponse {
|
||||
val credentials = AuthLoginPostRequest(emailAddress, password)
|
||||
|
||||
try {
|
||||
val response = authRepository.submitAuthLoginRequest(credentials)
|
||||
Log.d("AuthScreen", "Login successful! $response")
|
||||
return response
|
||||
} catch (err: Throwable) {
|
||||
// TODO...
|
||||
Log.e("AuthScreen", err.message!!)
|
||||
throw err
|
||||
}
|
||||
return authRepository.submitAuthLoginRequest(credentials)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
package ing.bikeshedengineer.debtpirate.data.remote.model.auth
|
||||
|
||||
data class AuthLoginPostRequest(val emailAddress: String, val password: String)
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AuthLoginPostRequest(@SerializedName("email") val emailAddress: String, val password: String)
|
||||
|
|
|
@ -30,7 +30,7 @@ class AccountManager(private val context: Activity) {
|
|||
AccountManagerResult.Success
|
||||
} catch (err: CreateCredentialNoCreateOptionException) {
|
||||
Log.w(
|
||||
"DebtPirate::AccountManager",
|
||||
"AccountManager",
|
||||
"Cannot store credentials; a Google account isn't associated with this device"
|
||||
)
|
||||
|
||||
|
@ -40,7 +40,7 @@ class AccountManager(private val context: Activity) {
|
|||
} catch (err: CreateCredentialException) {
|
||||
err.printStackTrace()
|
||||
Log.i(
|
||||
"DebtPirate::AccountManager",
|
||||
"AccountManager",
|
||||
"Unable to store credentials: ${err.message}"
|
||||
)
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.Retrofit
|
||||
|
||||
class InvalidCredentialsException(): Exception("Invalid email address or password")
|
||||
|
||||
class AuthRepositoryImpl(private val httpClient: Retrofit) : AuthRepository {
|
||||
private val authEndpoint: AuthEndpoint = this.httpClient.create(AuthEndpoint::class.java)
|
||||
|
||||
|
@ -27,7 +29,7 @@ class AuthRepositoryImpl(private val httpClient: Retrofit) : AuthRepository {
|
|||
val body =
|
||||
gson.fromJson<ApiResponse<Unit>>(response.errorBody()!!.charStream(), errorType)
|
||||
|
||||
throw Throwable(body!!.error!!)
|
||||
throw InvalidCredentialsException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue