feat: improvements at login

- password field is now recognised by the system keyboard
- TOTP six-digit input is now a numbers-only field
- document API functions related to login
- basic logout in API
- insecure MFA-less accounts can now log in (SAD!)
This commit is contained in:
Infi 2022-12-06 00:49:35 +01:00
parent 8b40f122d3
commit dff518bde7
4 changed files with 42 additions and 12 deletions

View File

@ -54,7 +54,9 @@ val RevoltHttp = HttpClient(OkHttp) {
object RevoltAPI {
const val TOKEN_HEADER_NAME = "x-session-token"
val userCache = mutableMapOf<String, CompleteUser>()
// discount caching solution(/-s)! LRU would be better but this is fine for now, until it's not...
val userCache =
mutableMapOf<String, CompleteUser>()
var selfId: String? = null
@ -71,9 +73,23 @@ object RevoltAPI {
}
}
/**
* Returns true if the user is logged in and the current user has been fetched at least once.
* Call [initialize] to fetch the current user first, else this will return false.
*/
fun isLoggedIn(): Boolean {
return selfId != null
}
/**
* Clears the API client's state completely.
*/
fun logout() {
selfId = null
sessionToken = ""
userCache.clear()
}
}
@kotlinx.serialization.Serializable

View File

@ -1,10 +1,12 @@
package chat.revolt.components.generic
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
@ -15,13 +17,14 @@ fun FormTextField(
label: String,
onChange: (it: String) -> Unit,
modifier: Modifier = Modifier,
password: Boolean = false,
type: KeyboardType = KeyboardType.Text,
) {
TextField(
value = value,
onValueChange = onChange,
singleLine = true,
visualTransformation = if (password) PasswordVisualTransformation() else VisualTransformation.None,
keyboardOptions = KeyboardOptions(keyboardType = type),
visualTransformation = if (type == KeyboardType.Password) PasswordVisualTransformation() else VisualTransformation.None,
label = { Text(label) },
modifier = modifier
)

View File

@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -22,6 +23,7 @@ import chat.revolt.R
import chat.revolt.api.REVOLT_SUPPORT
import chat.revolt.api.routes.account.EmailPasswordAssessment
import chat.revolt.api.routes.account.negotiateAuthentication
import chat.revolt.api.routes.user.fetchSelfWithNewToken
import chat.revolt.components.generic.AnyLink
import chat.revolt.components.generic.FormTextField
import chat.revolt.components.generic.Weblink
@ -40,9 +42,9 @@ class LoginViewModel() : ViewModel() {
val error: String?
get() = _error
private var _navigateToMfa by mutableStateOf(false)
val navigateToMfa: Boolean
get() = _navigateToMfa
private var _navigateTo by mutableStateOf<String?>(null)
val navigateTo: String?
get() = _navigateTo
private var _mfaResponse by mutableStateOf<EmailPasswordAssessment?>(null)
val mfaResponse: EmailPasswordAssessment?
@ -60,19 +62,21 @@ class LoginViewModel() : ViewModel() {
if (response.proceedMfa) {
Log.d("Login", "MFA required. Navigating to MFA screen")
_mfaResponse = response
_navigateToMfa = true
_navigateTo = "mfa"
} else {
Log.d(
"Login",
"No MFA required. Login is complete! We have a session token: ${response.firstUserHints!!.token}"
)
fetchSelfWithNewToken(response.firstUserHints.token)
_navigateTo = "home"
}
}
}
}
fun mfaComplete() {
_navigateToMfa = false
fun navigationComplete() {
_navigateTo = null
}
fun setEmail(email: String) {
@ -89,7 +93,7 @@ fun LoginScreen(
navController: NavController,
viewModel: LoginViewModel = viewModel()
) {
if (viewModel.navigateToMfa) {
if (viewModel.navigateTo == "mfa") {
navController.navigate(
"setup/mfa/${viewModel.mfaResponse!!.mfaSpec!!.ticket}/${
viewModel.mfaResponse!!.mfaSpec!!.allowedMethods.joinToString(
@ -97,7 +101,12 @@ fun LoginScreen(
)
}"
)
viewModel.mfaComplete()
viewModel.navigationComplete()
} else if (viewModel.navigateTo == "home") {
navController.navigate("chat/home") {
popUpTo("setup/greeting") { inclusive = true }
}
viewModel.navigationComplete()
}
Column(
@ -143,7 +152,7 @@ fun LoginScreen(
FormTextField(
value = viewModel.password,
label = stringResource(R.string.password),
password = true,
type = KeyboardType.Password,
onChange = { viewModel.setPassword(it) })
AnyLink(

View File

@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -197,6 +198,7 @@ fun MfaScreen(
FormTextField(
label = stringResource(R.string.mfa_totp_code),
onChange = { viewModel.setTotpCode(it) },
type = KeyboardType.Number,
value = viewModel.totpCode,
)