Skip to content

Commit beac6c7

Browse files
Merge pull request #109 from Web3Auth/feat/PD-4386_ManageMFA
feat: manage MFA
2 parents ff63a61 + 09aa3c2 commit beac6c7

File tree

7 files changed

+173
-26
lines changed

7 files changed

+173
-26
lines changed

app/src/main/java/com/web3auth/app/MainActivity.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemClickListener {
123123
val launchWalletButton = findViewById<Button>(R.id.launchWalletButton)
124124
val signMsgButton = findViewById<Button>(R.id.signMsgButton)
125125
val btnSetUpMfa = findViewById<Button>(R.id.btnSetUpMfa)
126+
val btnManageMfa = findViewById<Button>(R.id.btn_manageMfa)
126127
val spinner = findViewById<TextInputLayout>(R.id.verifierList)
127128
val hintEmailEditText = findViewById<EditText>(R.id.etEmailHint)
128129
var key: String? = null
@@ -145,6 +146,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemClickListener {
145146
launchWalletButton.visibility = View.VISIBLE
146147
signMsgButton.visibility = View.VISIBLE
147148
btnSetUpMfa.visibility = View.VISIBLE
149+
btnManageMfa.visibility = View.VISIBLE
148150
spinner.visibility = View.GONE
149151
hintEmailEditText.visibility = View.GONE
150152
} else {
@@ -153,6 +155,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemClickListener {
153155
signInButton.visibility = View.VISIBLE
154156
signOutButton.visibility = View.GONE
155157
btnSetUpMfa.visibility = View.GONE
158+
btnManageMfa.visibility = View.GONE
156159
launchWalletButton.visibility = View.GONE
157160
signMsgButton.visibility = View.GONE
158161
spinner.visibility = View.VISIBLE
@@ -184,7 +187,7 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemClickListener {
184187
clientId = "d84f6xvbdV75VTGmHiMWfZLeSPk8M07C",
185188
)
186189
),
187-
buildEnv = BuildEnv.PRODUCTION,
190+
buildEnv = BuildEnv.TESTING,
188191
sessionTime = 86400,
189192
)
190193

@@ -272,6 +275,18 @@ class MainActivity : AppCompatActivity(), AdapterView.OnItemClickListener {
272275
}
273276
}
274277

278+
val btnManageMfa = findViewById<Button>(R.id.btn_manageMfa)
279+
btnManageMfa.setOnClickListener {
280+
val manageMfaCf = web3Auth.manageMFA()
281+
manageMfaCf.whenComplete { _, error ->
282+
if (error == null) {
283+
Log.d("MainActivity_Web3Auth", "MFA manage successfully")
284+
} else {
285+
Log.d("MainActivity_Web3Auth", error.message ?: "Something went wrong")
286+
}
287+
}
288+
}
289+
275290
val spinner = findViewById<AutoCompleteTextView>(R.id.spinnerTextView)
276291
val loginVerifierList: List<String> = verifierList.map { item ->
277292
item.name

app/src/main/res/layout/activity_main.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,16 @@
114114
app:layout_constraintBottom_toBottomOf="parent"
115115
app:layout_constraintStart_toStartOf="parent" />
116116

117+
<Button
118+
android:id="@+id/btn_manageMfa"
119+
android:layout_width="wrap_content"
120+
android:layout_height="wrap_content"
121+
android:layout_marginStart="10dp"
122+
android:text="Manage MFA"
123+
android:textAllCaps="false"
124+
android:visibility="gone"
125+
app:layout_constraintBottom_toBottomOf="parent"
126+
app:layout_constraintEnd_toEndOf="parent"
127+
app:layout_constraintStart_toStartOf="parent" />
128+
117129
</androidx.constraintlayout.widget.ConstraintLayout>

core/src/main/java/com/web3auth/core/Web3Auth.kt

Lines changed: 101 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.web3auth.core.types.LoginConfigItem
2121
import com.web3auth.core.types.LoginParams
2222
import com.web3auth.core.types.MFALevel
2323
import com.web3auth.core.types.REDIRECT_URL
24+
import com.web3auth.core.types.RedirectResponse
2425
import com.web3auth.core.types.RequestData
2526
import com.web3auth.core.types.SessionResponse
2627
import com.web3auth.core.types.SignMessage
@@ -48,13 +49,14 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
4849

4950
private lateinit var loginCompletableFuture: CompletableFuture<Web3AuthResponse>
5051
private lateinit var enableMfaCompletableFuture: CompletableFuture<Boolean>
52+
private lateinit var manageMfaCompletableFuture: CompletableFuture<Boolean>
5153
private lateinit var signMsgCF: CompletableFuture<SignResponse>
5254

5355
private var web3AuthResponse: Web3AuthResponse? = null
5456
private var web3AuthOption = web3AuthOptions
5557
private var sessionManager: SessionManager = SessionManager(
5658
context,
57-
web3AuthOptions.sessionTime ?: 86400,
59+
web3AuthOptions.sessionTime ?: 30 * 86400,
5860
web3AuthOptions.redirectUrl.toString()
5961
)
6062

@@ -75,7 +77,8 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
7577
buildEnv = web3AuthOption.buildEnv?.name?.lowercase(Locale.ROOT),
7678
mfaSettings = web3AuthOption.mfaSettings?.let { gson.toJson(it) },
7779
sessionTime = web3AuthOption.sessionTime,
78-
originData = web3AuthOption.originData?.let { gson.toJson(it) }
80+
originData = web3AuthOption.originData?.let { gson.toJson(it) },
81+
dashboardUrl = web3AuthOption.dashboardUrl
7982
)
8083
}
8184

@@ -108,36 +111,63 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
108111
actionType: String, params: LoginParams?
109112
) {
110113
val sdkUrl = Uri.parse(web3AuthOption.sdkUrl)
111-
val initOptions = JSONObject(gson.toJson(getInitOptions()))
112-
val initParams = JSONObject(gson.toJson(getInitParams(params)))
114+
115+
val initOptions = if (actionType == "manage_mfa") {
116+
getInitOptions().copy(redirectUrl = getInitOptions().dashboardUrl)
117+
} else {
118+
getInitOptions()
119+
}
120+
121+
val initParams = if (actionType == "manage_mfa") {
122+
initOptions.dashboardUrl?.let { getInitParams(params).copy(redirectUrl = it) }
123+
} else {
124+
getInitParams(params)
125+
}
126+
127+
val initOptionsJson = JSONObject(gson.toJson(initOptions))
128+
val initParamsJson = JSONObject(gson.toJson(initParams))
129+
130+
val sessionId = SessionManager.generateRandomSessionKey()
113131

114132
val paramMap = JSONObject()
115133
paramMap.put(
116-
"options", initOptions
134+
"options", initOptionsJson
117135
)
118136
paramMap.put("actionType", actionType)
119137

120-
if (actionType == "enable_mfa") {
138+
if (actionType == "enable_mfa" || actionType == "manage_mfa") {
121139
val userInfo = web3AuthResponse?.userInfo
122-
initParams.put("loginProvider", userInfo?.typeOfLogin)
140+
initParamsJson.put("loginProvider", userInfo?.typeOfLogin)
123141
var extraOptionsString = ""
124142
var existingExtraLoginOptions = ExtraLoginOptions()
125-
if (initParams.has("extraLoginOptions")) {
126-
extraOptionsString = initParams.getString("extraLoginOptions")
143+
if (initParamsJson.has("extraLoginOptions")) {
144+
extraOptionsString = initParamsJson.getString("extraLoginOptions")
127145
existingExtraLoginOptions =
128146
gson.fromJson(extraOptionsString, ExtraLoginOptions::class.java)
129147
}
130148
existingExtraLoginOptions.login_hint = userInfo?.verifierId
131-
initParams.put("extraLoginOptions", gson.toJson(existingExtraLoginOptions))
132-
initParams.put("mfaLevel", MFALevel.MANDATORY.name.lowercase(Locale.ROOT))
149+
initParamsJson.put("extraLoginOptions", gson.toJson(existingExtraLoginOptions))
150+
initParamsJson.put("mfaLevel", MFALevel.MANDATORY.name.lowercase(Locale.ROOT))
151+
val loginIdObject = mapOf("loginId" to sessionId, "platform" to "android")
152+
initParamsJson.put(
153+
"appState",
154+
gson.toJson(loginIdObject).toByteArray(Charsets.UTF_8).toBase64URLString()
155+
)
156+
initParamsJson.put("dappUrl", web3AuthOption.redirectUrl)
133157
paramMap.put("sessionId", sessionManager.getSessionId())
134158
}
135-
paramMap.put("params", initParams)
159+
paramMap.put("params", initParamsJson)
160+
161+
val jsonObject = JSONObject(paramMap.toString())
136162

137-
val loginIdCf = getLoginId(paramMap)
163+
var paramsString = jsonObject.toString()
164+
paramsString = paramsString.replace("\\/", "/")
165+
166+
val loginIdCf = getLoginId(sessionId, paramsString)
138167
loginIdCf.whenComplete { loginId, error ->
139168
if (error == null) {
140169
val jsonObject = mapOf("loginId" to loginId)
170+
141171
val hash = "b64Params=" + gson.toJson(jsonObject).toByteArray(Charsets.UTF_8)
142172
.toBase64URLString()
143173

@@ -205,20 +235,38 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
205235
val hashUri = Uri.parse(uri?.host + "?" + uri?.fragment)
206236
val error = uri?.getQueryParameter("error")
207237
if (error != null) {
208-
loginCompletableFuture.completeExceptionally(UnKnownException(error))
238+
if (::loginCompletableFuture.isInitialized) loginCompletableFuture.completeExceptionally(
239+
UnKnownException(error)
240+
)
241+
209242
if (::enableMfaCompletableFuture.isInitialized) enableMfaCompletableFuture.completeExceptionally(
210243
UnKnownException(error)
211244
)
245+
246+
if (::manageMfaCompletableFuture.isInitialized) manageMfaCompletableFuture.completeExceptionally(
247+
UnKnownException(error)
248+
)
212249
return
213250
}
214251

215252
val b64Params = hashUri.getQueryParameter("b64Params")
216253
if (b64Params.isNullOrBlank()) {
217254
throwLoginError(ErrorCode.INVALID_LOGIN)
218255
throwEnableMFAError(ErrorCode.INVALID_LOGIN)
256+
throwManageMFAError(ErrorCode.INVALID_LOGIN)
219257
return
220258
}
221259
val b64ParamString = decodeBase64URLString(b64Params).toString(Charsets.UTF_8)
260+
261+
if (b64ParamString.contains("actionType")) {
262+
val response = gson.fromJson(b64ParamString, RedirectResponse::class.java)
263+
if (response.actionType == "manage_mfa") {
264+
if (::manageMfaCompletableFuture.isInitialized)
265+
manageMfaCompletableFuture.complete(true)
266+
return
267+
}
268+
}
269+
222270
val sessionResponse = gson.fromJson(b64ParamString, SessionResponse::class.java)
223271
val sessionId = sessionResponse.sessionId
224272

@@ -235,9 +283,11 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
235283
if (web3AuthResponse?.error?.isNotBlank() == true) {
236284
throwLoginError(ErrorCode.SOMETHING_WENT_WRONG)
237285
throwEnableMFAError(ErrorCode.SOMETHING_WENT_WRONG)
286+
throwManageMFAError(ErrorCode.SOMETHING_WENT_WRONG)
238287
} else if (web3AuthResponse?.privKey.isNullOrBlank() && web3AuthResponse?.factorKey.isNullOrBlank()) {
239288
throwLoginError(ErrorCode.SOMETHING_WENT_WRONG)
240289
throwEnableMFAError(ErrorCode.SOMETHING_WENT_WRONG)
290+
throwManageMFAError(ErrorCode.SOMETHING_WENT_WRONG)
241291
} else {
242292
web3AuthResponse?.sessionId?.let {
243293
SessionManager.saveSessionIdToStorage(it)
@@ -251,7 +301,9 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
251301
web3AuthResponse?.userInfo?.dappShare!!,
252302
)
253303
}
254-
loginCompletableFuture.complete(web3AuthResponse)
304+
if (::loginCompletableFuture.isInitialized)
305+
loginCompletableFuture.complete(web3AuthResponse)
306+
255307
if (::enableMfaCompletableFuture.isInitialized)
256308
enableMfaCompletableFuture.complete(true)
257309
}
@@ -263,6 +315,7 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
263315
} else {
264316
throwLoginError(ErrorCode.SOMETHING_WENT_WRONG)
265317
throwEnableMFAError(ErrorCode.SOMETHING_WENT_WRONG)
318+
throwManageMFAError(ErrorCode.SOMETHING_WENT_WRONG)
266319
}
267320
}
268321

@@ -334,6 +387,22 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
334387
return enableMfaCompletableFuture
335388
}
336389

390+
391+
fun manageMFA(loginParams: LoginParams? = null): CompletableFuture<Boolean> {
392+
manageMfaCompletableFuture = CompletableFuture()
393+
if (web3AuthResponse?.userInfo?.isMfaEnabled == false) {
394+
throwManageMFAError(ErrorCode.MFA_NOT_ENABLED)
395+
return manageMfaCompletableFuture
396+
}
397+
val sessionId = sessionManager.getSessionId()
398+
if (sessionId.isBlank()) {
399+
throwManageMFAError(ErrorCode.NOUSERFOUND)
400+
return manageMfaCompletableFuture
401+
}
402+
processRequest("manage_mfa", loginParams)
403+
return manageMfaCompletableFuture
404+
}
405+
337406
/**
338407
* Authorize User session in order to avoid re-login
339408
*/
@@ -437,11 +506,10 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
437506
* @param jsonObject The JSONObject from which to retrieve the login ID.
438507
* @return A CompletableFuture<String> representing the asynchronous operation, containing the login ID.
439508
*/
440-
private fun getLoginId(jsonObject: JSONObject): CompletableFuture<String> {
441-
val sessionId = SessionManager.generateRandomSessionKey()
509+
private fun getLoginId(sessionId: String, jsonObject: String): CompletableFuture<String> {
442510
sessionManager.setSessionId(sessionId)
443511
return sessionManager.createSession(
444-
jsonObject.toString(),
512+
jsonObject,
445513
baseContext,
446514
)
447515
}
@@ -471,8 +539,8 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
471539
paramMap.put(
472540
"options", initOptions
473541
)
474-
475-
val loginIdCf = getLoginId(paramMap)
542+
val sessionId = SessionManager.generateRandomSessionKey()
543+
val loginIdCf = getLoginId(sessionId, paramMap.toString())
476544

477545
loginIdCf.whenComplete { loginId, error ->
478546
if (error == null) {
@@ -536,7 +604,8 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
536604
"options", initOptions
537605
)
538606

539-
val loginIdCf = getLoginId(paramMap)
607+
val loginId = SessionManager.generateRandomSessionKey()
608+
val loginIdCf = getLoginId(loginId, paramMap.toString())
540609

541610
loginIdCf.whenComplete { loginId, error ->
542611
if (error == null) {
@@ -590,6 +659,17 @@ class Web3Auth(web3AuthOptions: Web3AuthOptions, context: Context) : WebViewResu
590659
)
591660
}
592661

662+
private fun throwManageMFAError(error: ErrorCode) {
663+
if (::manageMfaCompletableFuture.isInitialized)
664+
manageMfaCompletableFuture.completeExceptionally(
665+
Exception(
666+
Web3AuthError.getError(
667+
error
668+
)
669+
)
670+
)
671+
}
672+
593673
private fun throwLoginError(error: ErrorCode) {
594674
if (::loginCompletableFuture.isInitialized) {
595675
loginCompletableFuture.completeExceptionally(

core/src/main/java/com/web3auth/core/types/InitOptions.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ package com.web3auth.core.types
33
data class InitOptions(
44
val clientId: String,
55
val network: String,
6-
val redirectUrl: String? = null,
6+
var redirectUrl: String? = null,
77
val whiteLabel: String? = null,
88
val loginConfig: String? = null,
99
val buildEnv: String? = null,
1010
val mfaSettings: String? = null,
1111
val sessionTime: Int? = null,
12-
val originData: String? = null
12+
val originData: String? = null,
13+
val dashboardUrl: String? = null,
1314
)

core/src/main/java/com/web3auth/core/types/SessionResponse.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ package com.web3auth.core.types
33
data class SessionResponse(
44
val sessionId: String
55
)
6+
7+
data class RedirectResponse(
8+
val actionType: String
9+
)

core/src/main/java/com/web3auth/core/types/Web3AuthError.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,23 @@ object Web3AuthError {
1919
ErrorCode.RUNTIME_ERROR -> {
2020
"Runtime Error"
2121
}
22+
2223
ErrorCode.APP_CANCELLED -> {
2324
"App Cancelled"
2425
}
26+
2527
ErrorCode.INVALID_LOGIN -> {
2628
"Invalid Login"
2729
}
30+
2831
ErrorCode.MFA_ALREADY_ENABLED -> {
2932
"MFA already enabled"
3033
}
34+
35+
ErrorCode.MFA_NOT_ENABLED -> {
36+
"MFA is not enabled. Please enable MFA first."
37+
}
38+
3139
ErrorCode.USER_CANCELLED -> {
3240
"User Cancelled"
3341
}
@@ -44,5 +52,6 @@ enum class ErrorCode {
4452
SOMETHING_WENT_WRONG,
4553
INVALID_LOGIN,
4654
MFA_ALREADY_ENABLED,
55+
MFA_NOT_ENABLED,
4756
USER_CANCELLED
4857
}

0 commit comments

Comments
 (0)