Skip to content

Commit

Permalink
feat: Login through username in the new app (openedx#181)
Browse files Browse the repository at this point in the history
- Add username support for the authentication

fixes: LEARNER-9782
  • Loading branch information
farhan-arshad-dev authored Jan 19, 2024
1 parent 4d97d4f commit afcda48
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,34 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
Expand All @@ -28,18 +49,26 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.openedx.auth.presentation.ui.LoginTextField
import org.openedx.core.AppUpdateState
import org.openedx.core.R
import org.openedx.core.UIMessage
import org.openedx.core.ui.*
import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRequiredScreen
import org.openedx.core.ui.BackBtn
import org.openedx.core.ui.HandleUIMessage
import org.openedx.core.ui.OpenEdXButton
import org.openedx.core.ui.WindowSize
import org.openedx.core.ui.WindowType
import org.openedx.core.ui.displayCutoutForLandscape
import org.openedx.core.ui.rememberWindowSize
import org.openedx.core.ui.statusBarsInset
import org.openedx.core.ui.theme.OpenEdXTheme
import org.openedx.core.ui.theme.appColors
import org.openedx.core.ui.theme.appShapes
import org.openedx.core.ui.theme.appTypography
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.openedx.core.presentation.global.app_upgrade.AppUpgradeRequiredScreen
import org.openedx.core.AppUpdateState
import org.openedx.core.ui.windowSizeValue
import org.openedx.auth.R as authR
import org.openedx.core.R

class RestorePasswordFragment : Fragment() {

Expand Down Expand Up @@ -226,6 +255,8 @@ private fun RestorePasswordScreen(
Spacer(modifier = Modifier.height(32.dp))
LoginTextField(
modifier = Modifier.fillMaxWidth(),
title = stringResource(id = authR.string.auth_email),
description = stringResource(id = authR.string.auth_example_email),
onValueChanged = {
email = it
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ class SignInViewModel(
}

fun login(username: String, password: String) {
if (!validator.isEmailValid(username)) {
if (!validator.isEmailOrUserNameValid(username)) {
_uiMessage.value =
UIMessage.SnackBarMessage(resourceManager.getString(R.string.auth_invalid_email))
UIMessage.SnackBarMessage(resourceManager.getString(R.string.auth_invalid_email_username))
return
}
if (!validator.isPasswordValid(password)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ private fun AuthForm(
LoginTextField(
modifier = Modifier
.fillMaxWidth(),
title = stringResource(id = R.string.auth_email_username),
description = stringResource(id = R.string.auth_enter_email_username),
onValueChanged = {
login = it
})
Expand Down
6 changes: 4 additions & 2 deletions auth/src/main/java/org/openedx/auth/presentation/ui/AuthUI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ fun OptionalFields(
@Composable
fun LoginTextField(
modifier: Modifier = Modifier,
title: String,
description: String,
onValueChanged: (String) -> Unit,
imeAction: ImeAction = ImeAction.Next,
keyboardActions: (FocusManager) -> Unit = { it.moveFocus(FocusDirection.Down) }
Expand All @@ -226,7 +228,7 @@ fun LoginTextField(
val focusManager = LocalFocusManager.current
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.auth_email),
text = title,
color = MaterialTheme.appColors.textPrimary,
style = MaterialTheme.appTypography.labelLarge
)
Expand All @@ -244,7 +246,7 @@ fun LoginTextField(
shape = MaterialTheme.appShapes.textFieldShape,
placeholder = {
Text(
text = stringResource(id = R.string.auth_example_email),
text = description,
color = MaterialTheme.appColors.textFieldHint,
style = MaterialTheme.appTypography.bodyMedium
)
Expand Down
3 changes: 3 additions & 0 deletions auth/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<string name="auth_forgot_password">Forgot password?</string>
<string name="auth_email">Email</string>
<string name="auth_invalid_email">Invalid email</string>
<string name="auth_email_username" tools:ignore="MissingTranslation">Email or Username</string>
<string name="auth_invalid_email_username" tools:ignore="MissingTranslation">Invalid email or username</string>
<string name="auth_invalid_password">Password is too short</string>
<string name="auth_welcome_back">Welcome back! Please authorize to continue.</string>
<string name="auth_show_optional_fields">Show optional fields</string>
Expand All @@ -19,6 +21,7 @@
<string name="auth_check_your_email">Check your email</string>
<string name="auth_restore_password_success">We have sent a password recover instructions to your email %s</string>
<string name="auth_example_email" translatable="false">username@domain.com</string>
<string name="auth_enter_email_username" tools:ignore="MissingTranslation">Enter email or username</string>
<string name="auth_enter_password">Enter password</string>
<string name="auth_create_new_account">Create new account.</string>
<string name="auth_google" tools:ignore="ExtraTranslation">Sign in with Google</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class SignInViewModelTest {
private val invalidCredential = "Invalid credentials"
private val noInternet = "Slow or no internet connection"
private val somethingWrong = "Something went wrong"
private val invalidEmail = "Invalid email"
private val invalidEmailOrUsername = "Invalid email or username"
private val invalidPassword = "Password too short"

private val user = User(0, "", "", "")
Expand All @@ -74,7 +74,7 @@ class SignInViewModelTest {
every { resourceManager.getString(CoreRes.string.core_error_invalid_grant) } returns invalidCredential
every { resourceManager.getString(CoreRes.string.core_error_no_connection) } returns noInternet
every { resourceManager.getString(CoreRes.string.core_error_unknown_error) } returns somethingWrong
every { resourceManager.getString(R.string.auth_invalid_email) } returns invalidEmail
every { resourceManager.getString(R.string.auth_invalid_email_username) } returns invalidEmailOrUsername
every { resourceManager.getString(R.string.auth_invalid_password) } returns invalidPassword
every { appUpgradeNotifier.notifier } returns emptyFlow()
every { config.isPreLoginExperienceEnabled() } returns false
Expand All @@ -91,7 +91,7 @@ class SignInViewModelTest {

@Test
fun `login empty credentials validation error`() = runTest {
every { validator.isEmailValid(any()) } returns false
every { validator.isEmailOrUserNameValid(any()) } returns false
every { preferencesManager.user } returns user
every { analytics.setUserIdForSession(any()) } returns Unit
val viewModel = SignInViewModel(
Expand All @@ -113,14 +113,14 @@ class SignInViewModelTest {

val message = viewModel.uiMessage.value as UIMessage.SnackBarMessage
val uiState = viewModel.uiState.value
assertEquals(invalidEmail, message.message)
assertEquals(invalidEmailOrUsername, message.message)
assertFalse(uiState.showProgress)
assertFalse(uiState.loginSuccess)
}

@Test
fun `login invalid email validation error`() = runTest {
every { validator.isEmailValid(any()) } returns false
every { validator.isEmailOrUserNameValid(any()) } returns false
every { preferencesManager.user } returns user
every { analytics.setUserIdForSession(any()) } returns Unit
val viewModel = SignInViewModel(
Expand All @@ -142,14 +142,14 @@ class SignInViewModelTest {

val message = viewModel.uiMessage.value as UIMessage.SnackBarMessage
val uiState = viewModel.uiState.value
assertEquals(invalidEmail, message.message)
assertEquals(invalidEmailOrUsername, message.message)
assertFalse(uiState.showProgress)
assertFalse(uiState.loginSuccess)
}

@Test
fun `login empty password validation error`() = runTest {
every { validator.isEmailValid(any()) } returns true
every { validator.isEmailOrUserNameValid(any()) } returns true
every { validator.isPasswordValid(any()) } returns false
every { preferencesManager.user } returns user
every { analytics.setUserIdForSession(any()) } returns Unit
Expand Down Expand Up @@ -180,7 +180,7 @@ class SignInViewModelTest {

@Test
fun `login invalid password validation error`() = runTest {
every { validator.isEmailValid(any()) } returns true
every { validator.isEmailOrUserNameValid(any()) } returns true
every { validator.isPasswordValid(any()) } returns false
every { preferencesManager.user } returns user
every { analytics.setUserIdForSession(any()) } returns Unit
Expand Down Expand Up @@ -211,7 +211,7 @@ class SignInViewModelTest {

@Test
fun `login success`() = runTest {
every { validator.isEmailValid(any()) } returns true
every { validator.isEmailOrUserNameValid(any()) } returns true
every { validator.isPasswordValid(any()) } returns true
every { analytics.userLoginEvent(any()) } returns Unit
every { preferencesManager.user } returns user
Expand Down Expand Up @@ -245,7 +245,7 @@ class SignInViewModelTest {

@Test
fun `login network error`() = runTest {
every { validator.isEmailValid(any()) } returns true
every { validator.isEmailOrUserNameValid(any()) } returns true
every { validator.isPasswordValid(any()) } returns true
every { preferencesManager.user } returns user
every { analytics.setUserIdForSession(any()) } returns Unit
Expand Down Expand Up @@ -279,7 +279,7 @@ class SignInViewModelTest {

@Test
fun `login invalid grant error`() = runTest {
every { validator.isEmailValid(any()) } returns true
every { validator.isEmailOrUserNameValid(any()) } returns true
every { validator.isPasswordValid(any()) } returns true
every { preferencesManager.user } returns user
every { analytics.setUserIdForSession(any()) } returns Unit
Expand Down Expand Up @@ -313,7 +313,7 @@ class SignInViewModelTest {

@Test
fun `login unknown exception`() = runTest {
every { validator.isEmailValid(any()) } returns true
every { validator.isEmailOrUserNameValid(any()) } returns true
every { validator.isPasswordValid(any()) } returns true
every { preferencesManager.user } returns user
every { analytics.setUserIdForSession(any()) } returns Unit
Expand Down
16 changes: 10 additions & 6 deletions core/src/main/java/org/openedx/core/Validator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import java.util.regex.Pattern

class Validator {

fun isEmailValid(email: String): Boolean {
val validEmailAddressRegex =
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE)
val matcher = validEmailAddressRegex.matcher(email)
return matcher.find()
fun isEmailOrUserNameValid(input: String): Boolean {
return if (input.contains("@")) {
val validEmailAddressRegex = Pattern.compile(
"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE
)
validEmailAddressRegex.matcher(input).find()
} else {
input.isNotBlank() && input.contains(" ").not()
}
}

fun isPasswordValid(password: String): Boolean {
return password.length >= 2
}

}
}

0 comments on commit afcda48

Please sign in to comment.