Skip to content

Commit d93073b

Browse files
committed
add captcha verification for phone login and registration
1 parent 420a178 commit d93073b

20 files changed

Lines changed: 453 additions & 44 deletions

app/proguard-rules.pro

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,8 @@
3434
# Agora RTC
3535
-keep class io.agora.**{*;}
3636
# Agora Whiteboard
37-
-keep class com.herewhite.** { *; }
37+
-keep class com.herewhite.** { *; }
38+
39+
-keepclassmembers class * {
40+
@android.webkit.JavascriptInterface <methods>;
41+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<meta name="viewport"
7+
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
8+
<title>认证码验证</title>
9+
<script>
10+
// 默认配置
11+
var CaptchaDefaultConfig = {
12+
region: "cn",
13+
prefix: "195gxh",
14+
sceneId: "nypf9bgg",
15+
language: "cn"
16+
};
17+
18+
// 从 Native 获取配置的统一方法
19+
function getNativeConfig() {
20+
var config = Object.assign({}, CaptchaDefaultConfig);
21+
if (window.captchaJsBridge && typeof window.captchaJsBridge.getConfig === 'function') {
22+
try {
23+
var nativeConfig = JSON.parse(window.captchaJsBridge.getConfig());
24+
Object.assign(config, nativeConfig);
25+
console.log("Native 配置加载成功:", JSON.stringify(config));
26+
} catch (e) {
27+
console.error("解析 Native 配置失败", e);
28+
}
29+
}
30+
return config;
31+
}
32+
33+
// 初始化阿里云验证码全局配置
34+
(function() {
35+
var config = getNativeConfig();
36+
window.AliyunCaptchaConfig = {
37+
region: config.region,
38+
prefix: config.prefix
39+
};
40+
})();
41+
</script>
42+
<script type="text/javascript" src="https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js"></script>
43+
<style>
44+
html, body {
45+
height: 100%;
46+
margin: 0;
47+
padding: 0;
48+
position: relative;
49+
}
50+
#captcha-element {
51+
position: absolute;
52+
left: 50%;
53+
top: 50%;
54+
transform: translate(-50%, -50%);
55+
z-index: 1000;
56+
}
57+
</style>
58+
</head>
59+
60+
<body>
61+
<div id="captcha-element"></div>
62+
<div id="captcha-button"></div>
63+
64+
<script>
65+
var captcha;
66+
var config = getNativeConfig();
67+
68+
window.initAliyunCaptcha({
69+
SceneId: config.sceneId,
70+
mode: 'popup',
71+
element: '#captcha-element',
72+
button: '#captcha-button',
73+
success: onSuccess,
74+
fail: onFail,
75+
onClose: onClose,
76+
getInstance: onGetInstance,
77+
slideStyle: {
78+
width: 320,
79+
height: 0,
80+
},
81+
language: config.language,
82+
});
83+
84+
function onGetInstance(instance) {
85+
captcha = instance;
86+
document.getElementById('captcha-button').click();
87+
}
88+
89+
function onSuccess(captchaVerifyParam) {
90+
window.captchaJsBridge && window.captchaJsBridge.getCaptchaVerifyParam(captchaVerifyParam);
91+
}
92+
93+
function onFail(result) {
94+
window.captchaJsBridge && window.captchaJsBridge.dispatchFail(JSON.stringify(result));
95+
}
96+
97+
function onClose() {
98+
window.captchaJsBridge && window.captchaJsBridge.dispatchClose();
99+
}
100+
</script>
101+
</body>
102+
</html>

app/src/main/java/io/agora/flat/MainApplication.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class MainApplication : Application(), CameraXConfig.Provider {
2525
CallingCodeManager.init(this)
2626
GlobalInstanceProvider.init(this)
2727
ProtocolUrlManager.init(this)
28-
WebView.setWebContentsDebuggingEnabled(isApkInDebug())
28+
WebView.setWebContentsDebuggingEnabled(true)
2929
WhiteboardView.setEntryUrl("file:///android_asset/flatboard/index.html")
3030
}
3131

app/src/main/java/io/agora/flat/common/FlatErrorCode.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ class FlatErrorCode {
8585
const val OAuthUUIDNotFound = 130000 // oauth uuid not found
8686
const val OAuthClientIDNotFound = 130001 // oauth client id not found
8787
const val OAuthSecretUUIDNotFound = 130002 // oauth secret uuid not found
88+
89+
const val CaptchaFailed = 150000 // captcha failed
90+
const val CaptchaInvalid = 150001 // captcha invalid
91+
const val CaptchaRequired = 150002 // captcha required
8892
}
8993

9094
object RTM {

app/src/main/java/io/agora/flat/common/error/FlatErrorHandler.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ object FlatErrorHandler {
6060

6161
FlatErrorCode.Web.RoomCreateLimit -> context.getString(R.string.pay_room_reached_limit)
6262

63+
FlatErrorCode.Web.CaptchaFailed -> context.getString(R.string.error_captcha_failed)
64+
FlatErrorCode.Web.CaptchaRequired -> context.getString(R.string.error_captcha_required)
65+
FlatErrorCode.Web.CaptchaInvalid -> context.getString(R.string.error_captcha_invalid)
66+
6367
else -> context.getString(R.string.error_string_known_network, "${error.code}")
6468
}
6569
}

app/src/main/java/io/agora/flat/data/AppEnv.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ class AppEnv @Inject constructor(@ApplicationContext context: Context) {
2727

2828
const val DEFAULT_JOIN_EARLY_MINUTES = 10
2929

30+
val DEFAULT_ALIYUN_CAPTCHA_CONFIG = AliyunCaptchaConfig(
31+
region = "cn",
32+
language = "cn",
33+
prefix = "195gxh",
34+
sceneId = "nypf9bgg",
35+
)
36+
3037
val ALL_BASE_URLS = listOf(
3138
"https://flat-web.whiteboard.agora.io",
3239
"https://web.flat.apprtc.cn",
@@ -51,6 +58,7 @@ class AppEnv @Inject constructor(@ApplicationContext context: Context) {
5158
logstore = "android",
5259
endpoint = "https://cn-hangzhou.log.aliyuncs.com",
5360
),
61+
aliyunCaptchaConfig = DEFAULT_ALIYUN_CAPTCHA_CONFIG,
5462
ossKey = "LTAI5tD4WSVBxAyfwVoaKTWr",
5563
wechatId = "wx09437693798bc108",
5664
whiteAppId = "cFjxAJjiEeuUQ0211QCRBw/mO9uJB_DiCIqug",
@@ -81,6 +89,7 @@ class AppEnv @Inject constructor(@ApplicationContext context: Context) {
8189
googleClientId = "273996094508-p97og69ojac5ja0khn1rvmi3tb7vgfgm.apps.googleusercontent.com",
8290

8391
loginConfig = LoginConfig(google = false),
92+
aliyunCaptchaConfig = DEFAULT_ALIYUN_CAPTCHA_CONFIG,
8493

8594
showIcp = true,
8695
showStreamAgreement = true,
@@ -100,6 +109,7 @@ class AppEnv @Inject constructor(@ApplicationContext context: Context) {
100109
logstore = "android",
101110
endpoint = "https://ap-southeast-1.log.aliyuncs.com",
102111
),
112+
aliyunCaptchaConfig = DEFAULT_ALIYUN_CAPTCHA_CONFIG.copy(region = "sgp", language = "en"),
103113
ossKey = "LTAI5tMwHQ1xyroeneA9XLh4",
104114
wechatId = "wx09437693798bc108",
105115
whiteAppId = "cFjxAJjiEeuUQ0211QCRBw/kndLTOWdG2qYcQ",
@@ -122,6 +132,7 @@ class AppEnv @Inject constructor(@ApplicationContext context: Context) {
122132
logstore = "android",
123133
endpoint = "https://ap-southeast-1.log.aliyuncs.com",
124134
),
135+
aliyunCaptchaConfig = DEFAULT_ALIYUN_CAPTCHA_CONFIG.copy(region = "sgp", language = "en"),
125136
ossKey = "LTAI5tMwHQ1xyroeneA9XLh4",
126137
wechatId = "wx09437693798bc108",
127138
whiteAppId = "n9q1oBxDEeyuBMn1qc0iFw/fLgNSEvdwKjlig",
@@ -182,6 +193,8 @@ class AppEnv @Inject constructor(@ApplicationContext context: Context) {
182193

183194
val loginConfig get() = currentEnvItem.loginConfig
184195

196+
val aliyunCaptchaConfig get() = currentEnvItem.aliyunCaptchaConfig ?: DEFAULT_ALIYUN_CAPTCHA_CONFIG
197+
185198
val phoneFirst get() = currentEnvItem.loginConfig.phoneFirst
186199

187200
val showIcp get() = currentEnvItem.showIcp
@@ -205,6 +218,7 @@ class AppEnv @Inject constructor(@ApplicationContext context: Context) {
205218
val region: String = "cn-hz",
206219

207220
val loginConfig: LoginConfig = LoginConfig(),
221+
val aliyunCaptchaConfig: AliyunCaptchaConfig? = null,
208222

209223
val showIcp: Boolean = false,
210224
val showStreamAgreement: Boolean = false,
@@ -225,4 +239,11 @@ data class LoginConfig(
225239
fun forceBindPhone(): Boolean {
226240
return sms && smsForce
227241
}
228-
}
242+
}
243+
244+
data class AliyunCaptchaConfig(
245+
val region: String,
246+
val prefix: String,
247+
val sceneId: String,
248+
val language: String,
249+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.agora.flat.data.model
2+
3+
data class PhoneCaptchaReq(
4+
val phone: String,
5+
val captchaVerifyParam: String,
6+
)

app/src/main/java/io/agora/flat/data/repository/UserRepository.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import io.agora.flat.data.model.EmailPasswordReq
1313
import io.agora.flat.data.model.EmailRegisterReq
1414
import io.agora.flat.data.model.LoginHistoryItem
1515
import io.agora.flat.data.model.LoginPlatform
16+
import io.agora.flat.data.model.PhoneCaptchaReq
1617
import io.agora.flat.data.model.PhonePasswordReq
1718
import io.agora.flat.data.model.PhoneRegisterReq
1819
import io.agora.flat.data.model.PhoneReq
@@ -138,9 +139,9 @@ class UserRepository @Inject constructor(
138139
/**
139140
* phone: +[country code][phone number]
140141
*/
141-
suspend fun requestLoginSmsCode(phone: String): Result<RespNoData> {
142+
suspend fun requestLoginSmsCode(phone: String, captchaVerifyParam: String): Result<RespNoData> {
142143
return withContext(Dispatchers.IO) {
143-
userService.requestSmsCode(PhoneReq(phone = phone)).toResult()
144+
userService.requestSmsCodeCaptcha(PhoneCaptchaReq(phone = phone, captchaVerifyParam = captchaVerifyParam)).toResult()
144145
}
145146
}
146147

@@ -331,9 +332,9 @@ class UserRepository @Inject constructor(
331332
// }
332333
// }
333334

334-
suspend fun requestRegisterSmsCode(phone: String): Result<RespNoData> {
335+
suspend fun requestRegisterSmsCode(phone: String, captchaVerifyParam: String): Result<RespNoData> {
335336
return withContext(Dispatchers.IO) {
336-
userService.requestRegisterSmsCode(PhoneReq(phone)).toResult()
337+
userService.requestRegisterSmsCodeCaptcha(PhoneCaptchaReq(phone = phone, captchaVerifyParam = captchaVerifyParam)).toResult()
337338
}
338339
}
339340

@@ -424,9 +425,9 @@ class UserRepository @Inject constructor(
424425
}
425426
}
426427

427-
suspend fun requestResetPhoneCode(phone: String): Result<RespNoData> {
428+
suspend fun requestResetPhoneCode(phone: String, captchaVerifyParam: String): Result<RespNoData> {
428429
return withContext(Dispatchers.IO) {
429-
userService.requestResetPhoneCode(PhoneReq(phone)).toResult()
430+
userService.requestResetPhoneCodeCaptcha(PhoneCaptchaReq(phone, captchaVerifyParam)).toResult()
430431
}
431432
}
432433

app/src/main/java/io/agora/flat/http/api/UserService.kt

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,14 @@ interface UserService {
3030
@Body req: AuthUUIDReq,
3131
): Call<BaseResp<UserInfoWithToken>>
3232

33-
@POST("v1/login/phone/sendMessage")
34-
fun requestSmsCode(
35-
@Body req: PhoneReq,
33+
// @POST("v1/login/phone/sendMessage")
34+
// fun requestSmsCode(
35+
// @Body req: PhoneReq,
36+
// ): Call<BaseResp<RespNoData>>
37+
38+
@POST("v1/login/phone/sendMessage/captcha")
39+
fun requestSmsCodeCaptcha(
40+
@Body req: PhoneCaptchaReq,
3641
): Call<BaseResp<RespNoData>>
3742

3843
@POST("v1/login/phone")
@@ -104,9 +109,14 @@ interface UserService {
104109
@Body req: RemoveBindingReq,
105110
): Call<BaseResp<UserTokenData>>
106111

107-
@POST("v2/register/phone/send-message")
108-
fun requestRegisterSmsCode(
109-
@Body req: PhoneReq,
112+
// @POST("v2/register/phone/send-message")
113+
// fun requestRegisterSmsCode(
114+
// @Body req: PhoneReq,
115+
// ): Call<BaseResp<RespNoData>>
116+
117+
@POST("v2/register/phone/send-message/captcha")
118+
fun requestRegisterSmsCodeCaptcha(
119+
@Body req: PhoneCaptchaReq,
110120
): Call<BaseResp<RespNoData>>
111121

112122
@POST("v2/register/phone")
@@ -144,9 +154,14 @@ interface UserService {
144154
@Body req: EmailRegisterReq,
145155
): Call<BaseResp<RespNoData>>
146156

147-
@POST("v2/reset/phone/send-message")
148-
fun requestResetPhoneCode(
149-
@Body req: PhoneReq,
157+
// @POST("v2/reset/phone/send-message")
158+
// fun requestResetPhoneCode(
159+
// @Body req: PhoneReq,
160+
// ): Call<BaseResp<RespNoData>>
161+
162+
@POST("v2/reset/phone/send-message/captcha")
163+
fun requestResetPhoneCodeCaptcha(
164+
@Body req: PhoneCaptchaReq,
150165
): Call<BaseResp<RespNoData>>
151166

152167
@POST("v2/reset/phone")

app/src/main/java/io/agora/flat/ui/activity/LoginActivity.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class LoginActivity : BaseComposeActivity() {
137137
}
138138

139139
is LoginUiAction.PhoneSendCode -> {
140-
viewModel.sendPhoneCode(action.phone)
140+
viewModel.sendPhoneCode(action.phone, action.captchaVerifyParam)
141141
}
142142

143143
is LoginUiAction.SignUpClick -> {
@@ -471,7 +471,8 @@ private fun PhoneLoginArea(
471471
SendCodeInput(
472472
code = inputState.code,
473473
onCodeChange = { onLoginInputChange(inputState.copy(code = it)) },
474-
onSendCode = { actioner(LoginUiAction.PhoneSendCode("${inputState.cc}${inputState.value}")) },
474+
onSendCode = {},
475+
onSendCodeCaptcha = { actioner(LoginUiAction.PhoneSendCode("${inputState.cc}${inputState.value}", it)) },
475476
ready = inputState.value.isValidPhone(),
476477
remainTime = inputState.remainTime,
477478
)

0 commit comments

Comments
 (0)