mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Enable EmailAuth support. (#6277)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
This commit is contained in:
parent
56fa8ceb5a
commit
2eed441462
9 changed files with 76 additions and 17 deletions
|
|
@ -65,6 +65,7 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
private val delegate: AppCompatDelegate by lazy {
|
||||
AppCompatDelegate.create(this, null)
|
||||
}
|
||||
private var lastLoginResult: LoginResult? = null
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -271,6 +272,7 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
showLoggingProgressBar()
|
||||
loginClient.doLogin(username,
|
||||
password,
|
||||
lastLoginResult,
|
||||
twoFactorCode,
|
||||
Locale.getDefault().language,
|
||||
object : LoginCallback {
|
||||
|
|
@ -280,9 +282,17 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
onLoginSuccess(loginResult)
|
||||
}
|
||||
|
||||
override fun twoFactorPrompt(caught: Throwable, token: String?) = runOnUiThread {
|
||||
override fun twoFactorPrompt(loginResult: LoginResult, caught: Throwable, token: String?) = runOnUiThread {
|
||||
Timber.d("Requesting 2FA prompt")
|
||||
progressDialog!!.dismiss()
|
||||
lastLoginResult = loginResult
|
||||
askUserForTwoFactorAuth()
|
||||
}
|
||||
|
||||
override fun emailAuthPrompt(loginResult: LoginResult, caught: Throwable, token: String?) {
|
||||
Timber.d("Requesting email auth prompt")
|
||||
progressDialog!!.dismiss()
|
||||
lastLoginResult = loginResult
|
||||
askUserForTwoFactorAuth()
|
||||
}
|
||||
|
||||
|
|
@ -341,12 +351,13 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
progressDialog!!.dismiss()
|
||||
with(binding!!) {
|
||||
twoFactorContainer.visibility = View.VISIBLE
|
||||
twoFactorContainer.hint = getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.email_auth_code else R.string._2fa_code)
|
||||
loginTwoFactor.visibility = View.VISIBLE
|
||||
loginTwoFactor.requestFocus()
|
||||
}
|
||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
|
||||
showMessageAndCancelDialog(R.string.login_failed_2fa_needed)
|
||||
showMessageAndCancelDialog(getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.login_failed_email_auth_needed else R.string.login_failed_2fa_needed))
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class CsrfTokenClient(
|
|||
try {
|
||||
if (retry > 0) {
|
||||
// Log in explicitly
|
||||
loginClient.loginBlocking(userName, password, "")
|
||||
loginClient.loginBlocking(userName, password)
|
||||
}
|
||||
|
||||
// Get CSRFToken response off the main thread.
|
||||
|
|
@ -92,6 +92,8 @@ class CsrfTokenClient(
|
|||
override fun failure(caught: Throwable?) = retryWithLogin(cb) { caught }
|
||||
|
||||
override fun twoFactorPrompt() = cb.twoFactorPrompt()
|
||||
|
||||
override fun emailAuthPrompt() = cb.emailAuthPrompt()
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -165,10 +167,17 @@ class CsrfTokenClient(
|
|||
}
|
||||
|
||||
override fun twoFactorPrompt(
|
||||
loginResult: LoginResult,
|
||||
caught: Throwable,
|
||||
token: String?,
|
||||
) = callback.twoFactorPrompt()
|
||||
|
||||
override fun emailAuthPrompt(
|
||||
loginResult: LoginResult,
|
||||
caught: Throwable,
|
||||
token: String?,
|
||||
) = callback.emailAuthPrompt()
|
||||
|
||||
// Should not happen here, but call the callback just in case.
|
||||
override fun passwordResetPrompt(token: String?) = callback.failure(LoginFailedException("Logged in with temporary password."))
|
||||
|
||||
|
|
@ -190,6 +199,8 @@ class CsrfTokenClient(
|
|||
fun failure(caught: Throwable?)
|
||||
|
||||
fun twoFactorPrompt()
|
||||
|
||||
fun emailAuthPrompt()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@ interface LoginCallback {
|
|||
fun success(loginResult: LoginResult)
|
||||
|
||||
fun twoFactorPrompt(
|
||||
loginResult: LoginResult,
|
||||
caught: Throwable,
|
||||
token: String?,
|
||||
)
|
||||
|
||||
fun emailAuthPrompt(
|
||||
loginResult: LoginResult,
|
||||
caught: Throwable,
|
||||
token: String?,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.auth.login
|
||||
|
||||
import android.text.TextUtils
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.EmailAuthResult
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
|
||||
import fr.free.nrw.commons.wikidata.WikidataConstants.WIKIPEDIA_URL
|
||||
|
|
@ -51,6 +52,7 @@ class LoginClient(
|
|||
password,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
response.body()!!.query()!!.loginToken(),
|
||||
userLanguage,
|
||||
cb,
|
||||
|
|
@ -75,6 +77,7 @@ class LoginClient(
|
|||
password: String,
|
||||
retypedPassword: String?,
|
||||
twoFactorCode: String?,
|
||||
emailAuthCode: String?,
|
||||
loginToken: String?,
|
||||
userLanguage: String,
|
||||
cb: LoginCallback,
|
||||
|
|
@ -82,7 +85,7 @@ class LoginClient(
|
|||
this.userLanguage = userLanguage
|
||||
|
||||
loginCall =
|
||||
if (twoFactorCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) {
|
||||
if (twoFactorCode.isNullOrEmpty() && emailAuthCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) {
|
||||
loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
|
||||
} else {
|
||||
loginInterface.postLogIn(
|
||||
|
|
@ -90,6 +93,7 @@ class LoginClient(
|
|||
password,
|
||||
retypedPassword,
|
||||
twoFactorCode,
|
||||
emailAuthCode,
|
||||
loginToken,
|
||||
userLanguage,
|
||||
true,
|
||||
|
|
@ -112,10 +116,18 @@ class LoginClient(
|
|||
when (loginResult) {
|
||||
is OAuthResult ->
|
||||
cb.twoFactorPrompt(
|
||||
loginResult,
|
||||
LoginFailedException(loginResult.message),
|
||||
loginToken,
|
||||
)
|
||||
|
||||
is EmailAuthResult ->
|
||||
cb.emailAuthPrompt(
|
||||
loginResult,
|
||||
LoginFailedException(loginResult.message),
|
||||
loginToken
|
||||
)
|
||||
|
||||
is ResetPasswordResult -> cb.passwordResetPrompt(loginToken)
|
||||
|
||||
is LoginResult.Result ->
|
||||
|
|
@ -147,6 +159,7 @@ class LoginClient(
|
|||
fun doLogin(
|
||||
username: String,
|
||||
password: String,
|
||||
lastLoginResult: LoginResult?,
|
||||
twoFactorCode: String,
|
||||
userLanguage: String,
|
||||
loginCallback: LoginCallback,
|
||||
|
|
@ -159,7 +172,10 @@ class LoginClient(
|
|||
) = if (response.isSuccessful) {
|
||||
val loginToken = response.body()?.query()?.loginToken()
|
||||
loginToken?.let {
|
||||
login(username, password, null, twoFactorCode, it, userLanguage, loginCallback)
|
||||
login(username, password, null,
|
||||
if (lastLoginResult is OAuthResult) twoFactorCode else null,
|
||||
if (lastLoginResult is EmailAuthResult) twoFactorCode else null,
|
||||
it, userLanguage, loginCallback)
|
||||
} ?: run {
|
||||
loginCallback.error(IOException("Failed to retrieve login token"))
|
||||
}
|
||||
|
|
@ -181,7 +197,8 @@ class LoginClient(
|
|||
fun loginBlocking(
|
||||
userName: String,
|
||||
password: String,
|
||||
twoFactorCode: String?,
|
||||
twoFactorCode: String? = null,
|
||||
emailAuthCode: String? = null
|
||||
) {
|
||||
val tokenResponse = getLoginToken().execute()
|
||||
if (tokenResponse
|
||||
|
|
@ -195,7 +212,7 @@ class LoginClient(
|
|||
|
||||
val loginToken = tokenResponse.body()?.query()?.loginToken()
|
||||
val tempLoginCall =
|
||||
if (twoFactorCode.isNullOrEmpty()) {
|
||||
if (twoFactorCode.isNullOrEmpty() && emailAuthCode.isNullOrEmpty()) {
|
||||
loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
|
||||
} else {
|
||||
loginInterface.postLogIn(
|
||||
|
|
@ -203,6 +220,7 @@ class LoginClient(
|
|||
password,
|
||||
null,
|
||||
twoFactorCode,
|
||||
emailAuthCode,
|
||||
loginToken,
|
||||
userLanguage,
|
||||
true,
|
||||
|
|
@ -214,7 +232,7 @@ class LoginClient(
|
|||
val loginResult = loginResponse.toLoginResult(password) ?: throw IOException("Unexpected response when logging in.")
|
||||
|
||||
if ("UI" == loginResult.status) {
|
||||
if (loginResult is OAuthResult) {
|
||||
if (loginResult is OAuthResult || loginResult is EmailAuthResult) {
|
||||
// TODO: Find a better way to boil up the warning about 2FA
|
||||
throw LoginFailedException(loginResult.message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ interface LoginInterface {
|
|||
@Field("password") pass: String?,
|
||||
@Field("retype") retypedPass: String?,
|
||||
@Field("OATHToken") twoFactorCode: String?,
|
||||
@Field("logintoken") token: String?,
|
||||
@Field("token") emailAuthToken: String?,
|
||||
@Field("logintoken") loginToken: String?,
|
||||
@Field("uselang") userLanguage: String?,
|
||||
@Field("logincontinue") loginContinue: Boolean,
|
||||
): Call<LoginResponse?>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.auth.login
|
|||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.EmailAuthResult
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.Result
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwServiceError
|
||||
|
|
@ -27,11 +28,13 @@ internal class ClientLogin {
|
|||
fun toLoginResult(password: String): LoginResult {
|
||||
var userMessage = message
|
||||
if ("UI" == status) {
|
||||
if (requests != null) {
|
||||
for (req in requests) {
|
||||
if ("MediaWiki\\Extension\\OATHAuth\\Auth\\TOTPAuthenticationRequest" == req.id()) {
|
||||
requests?.forEach { request ->
|
||||
request.id()?.let {
|
||||
if (it.endsWith("TOTPAuthenticationRequest")) {
|
||||
return OAuthResult(status, userName, password, message)
|
||||
} else if ("MediaWiki\\Auth\\PasswordAuthenticationRequest" == req.id()) {
|
||||
} else if (it.endsWith("EmailAuthAuthenticationRequest")) {
|
||||
return EmailAuthResult(status, userName, password, message)
|
||||
} else if (it.endsWith("PasswordAuthenticationRequest")) {
|
||||
return ResetPasswordResult(status, userName, password, message)
|
||||
}
|
||||
}
|
||||
|
|
@ -49,7 +52,7 @@ internal class Request {
|
|||
private val required: String? = null
|
||||
private val provider: String? = null
|
||||
private val account: String? = null
|
||||
private val fields: Map<String, RequestField>? = null
|
||||
internal val fields: Map<String, RequestField>? = null
|
||||
|
||||
fun id(): String? = id
|
||||
}
|
||||
|
|
@ -57,5 +60,5 @@ internal class Request {
|
|||
internal class RequestField {
|
||||
private val type: String? = null
|
||||
private val label: String? = null
|
||||
private val help: String? = null
|
||||
internal val help: String? = null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ sealed class LoginResult(
|
|||
message: String?,
|
||||
) : LoginResult(status, userName, password, message)
|
||||
|
||||
class EmailAuthResult(
|
||||
status: String,
|
||||
userName: String?,
|
||||
password: String?,
|
||||
message: String?,
|
||||
) : LoginResult(status, userName, password, message)
|
||||
|
||||
class ResetPasswordResult(
|
||||
status: String,
|
||||
userName: String?,
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@
|
|||
android:layout_marginEnd="@dimen/standard_gap"
|
||||
android:layout_marginRight="@dimen/standard_gap"
|
||||
android:layout_marginBottom="@dimen/standard_gap"
|
||||
android:hint="@string/_2fa_code"
|
||||
android:visibility="gone"
|
||||
app:passwordToggleEnabled="false"
|
||||
tools:visibility="visible">
|
||||
|
|
@ -154,9 +155,7 @@
|
|||
android:id="@+id/login_two_factor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/_2fa_code"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:inputType="number"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@
|
|||
<string name="login_failed_throttled">Too many unsuccessful attempts. Please try again in a few minutes.</string>
|
||||
<string name="login_failed_blocked">Sorry, this user has been blocked on Commons</string>
|
||||
<string name="login_failed_2fa_needed">You must provide your two factor authentication code.</string>
|
||||
<string name="login_failed_email_auth_needed">A login verification code has been sent to your email address. Please provide the code to log in.</string>
|
||||
<string name="login_failed_generic">Log-in failed</string>
|
||||
<string name="share_upload_button">Upload</string>
|
||||
<string name="multiple_share_base_title">Name this set</string>
|
||||
|
|
@ -218,6 +219,7 @@
|
|||
<string name="become_a_tester_description">Opt-in to our beta channel on Google Play and get early access to new features and bug fixes</string>
|
||||
<string name="beta_opt_in_link">https://play.google.com/apps/testing/fr.free.nrw.commons</string>
|
||||
<string name="_2fa_code">2FA Code</string>
|
||||
<string name="email_auth_code">Email verification code</string>
|
||||
<string name="logout_verification">Do you really want to logout?</string>
|
||||
<string name="mediaimage_failed">Media Image Failed</string>
|
||||
<string name="no_subcategory_found">No subcategories found</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue