Skip to content

Commit 0c82f80

Browse files
Update login
1 parent bb112f6 commit 0c82f80

4 files changed

Lines changed: 167 additions & 44 deletions

File tree

lib/Api/login_api.dart

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,9 @@ class LoginApi {
203203
ILogger.error("Twitee API",
204204
"Failed to check username: ${response?.statusCode} ${response?.data}");
205205
return ResponseResult.error(
206-
message: response?.data["errors"][0]["message"] ??
207-
"Failed to check username",
206+
message: (response?.data["errors"][0]["message"] ??
207+
"Failed to check username")
208+
.split("g;")[0].trim(),
208209
code: response?.data["errors"][0]["code"] ?? 500,
209210
statusCode: response?.statusCode ?? 500,
210211
);
@@ -273,8 +274,9 @@ class LoginApi {
273274
);
274275
if (response == null || response.statusCode != 200) {
275276
return ResponseResult.error(
276-
message: response?.data["errors"][0]["message"] ??
277-
"Failed to check alternative username",
277+
message: (response?.data["errors"][0]["message"] ??
278+
"Failed to check alternative username")
279+
.split("g;")[0].trim(),
278280
code: response?.data["errors"][0]["code"] ?? 500,
279281
statusCode: response?.statusCode ?? 500,
280282
);
@@ -317,8 +319,9 @@ class LoginApi {
317319
);
318320
if (response == null || response.statusCode != 200) {
319321
return ResponseResult.error(
320-
message: response?.data["errors"][0]["message"] ??
321-
"Failed to check password",
322+
message: (response?.data["errors"][0]["message"] ??
323+
"Failed to check password")
324+
.split("g;")[0].trim(),
322325
code: response?.data["errors"][0]["code"] ?? 500,
323326
statusCode: response?.statusCode ?? 500,
324327
);
@@ -333,6 +336,14 @@ class LoginApi {
333336
data: data["flow_token"],
334337
data2:
335338
UserInfo.fromJson(subtask['enter_text']['header']['user']));
339+
} else if (subtask["subtask_id"] ==
340+
LoginPhase.loginAcid.subTaskName) {
341+
return ResponseResult.success(
342+
message: "To check email",
343+
flag: LoginPhase.loginAcid,
344+
data: data["flow_token"],
345+
data2:
346+
UserInfo.fromJson(subtask['enter_text']['header']['user']));
336347
} else if (subtask["subtask_id"] ==
337348
LoginPhase.loginSuccess.subTaskName) {
338349
return ResponseResult.success(
@@ -375,7 +386,8 @@ class LoginApi {
375386
if (response == null || response.statusCode != 200) {
376387
return ResponseResult.error(
377388
message:
378-
response?.data["errors"][0]["message"] ?? "Failed to check 2FA",
389+
(response?.data["errors"][0]["message"] ?? "Failed to check 2FA")
390+
.split("g;")[0].trim(),
379391
code: response?.data["errors"][0]["code"] ?? 500,
380392
);
381393
}
@@ -393,6 +405,49 @@ class LoginApi {
393405
}
394406
}
395407

408+
static Future<ResponseResult> checkEmail(
409+
String guestToken, String flowToken, String code) async {
410+
try {
411+
ILogger.info("Twitee API", "Checking Email");
412+
final response = await RequestUtil.post(
413+
"/1.1/onboarding/task.json",
414+
data: {
415+
"flow_token": flowToken,
416+
"subtask_inputs": [
417+
{
418+
"subtask_id": "LoginAcid",
419+
"enter_text": {"text": code, "link": "next_link"}
420+
}
421+
]
422+
},
423+
options: Options(
424+
headers: {
425+
"x-guest-token": guestToken,
426+
},
427+
),
428+
);
429+
if (response == null || response.statusCode != 200) {
430+
return ResponseResult.error(
431+
message: (response?.data["errors"][0]["message"] ??
432+
"Failed to check email")
433+
.split("g;")[0].trim(),
434+
code: response?.data["errors"][0]["code"] ?? 500,
435+
);
436+
}
437+
final data = response.data as Map;
438+
if (data.containsKey("flow_token")) {
439+
return ResponseResult.success(
440+
message: "Checked email",
441+
data: data["flow_token"],
442+
);
443+
}
444+
return ResponseResult.error(message: "Failed to check email");
445+
} catch (e, t) {
446+
ILogger.error("Twitee", "Failed to check email", e, t);
447+
return ResponseResult.error(message: e.toString());
448+
}
449+
}
450+
396451
static Future<ResponseResult> fetchCsrfToken(String guestToken) async {
397452
try {
398453
ILogger.info("Twitee API", "Fetching CSRF token");

lib/Models/login_phase.dart

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,27 @@
1414
*/
1515

1616
enum LoginPhase {
17-
checkUsername(0, "LoginEnterUserIdentifierSSO"),
18-
denyLoginSubtask(-1, "DenyLoginSubtask"),
19-
arkoseLogin(-1, "ArkoseLogin"),
20-
checkAlternativeUsername(1, "LoginEnterAlternateIdentifierSubtask"),
21-
checkPassword(2, "LoginEnterPassword"),
22-
resetPassword(-1, "RedirectToPasswordReset"),
23-
check2FA(3, "LoginTwoFactorAuthChallenge"),
24-
checkAnother2FA(4, "LoginTwoFactorAuthChooseMethod"),
25-
cannotAccess2FA(-1, "login_security_key_not_supported_cta"),
26-
loginSuccess(5, "LoginSuccessSubtask");
17+
checkUsername("LoginEnterUserIdentifierSSO"),
18+
denyLoginSubtask("DenyLoginSubtask"),
19+
arkoseLogin("ArkoseLogin"),
20+
checkAlternativeUsername("LoginEnterAlternateIdentifierSubtask"),
21+
checkPassword("LoginEnterPassword"),
22+
resetPassword("RedirectToPasswordReset"),
23+
check2FA("LoginTwoFactorAuthChallenge"),
24+
loginAcid("LoginAcid"),
25+
checkAnother2FA("LoginTwoFactorAuthChooseMethod"),
26+
cannotAccess2FA("login_security_key_not_supported_cta"),
27+
loginSuccess("LoginSuccessSubtask");
2728

28-
final int phaseIndex;
2929
final String subTaskName;
3030

31-
const LoginPhase(this.phaseIndex, this.subTaskName);
31+
const LoginPhase(this.subTaskName);
3232

3333
static List<LoginPhase> phases = [
3434
checkUsername,
3535
checkAlternativeUsername,
3636
checkPassword,
37+
loginAcid,
3738
check2FA,
3839
checkAnother2FA,
3940
loginSuccess,

lib/Screens/Login/login_screen.dart

Lines changed: 90 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ import '../../Utils/asset_util.dart';
3737
import '../../Utils/enums.dart';
3838
import '../../Utils/request_util.dart';
3939
import '../../Utils/utils.dart';
40-
import '../../Widgets/Scaffold/custom_cupertino_route.dart';
4140
import '../../Widgets/Item/input_item.dart';
4241
import '../../Widgets/Item/item_builder.dart';
42+
import '../../Widgets/Scaffold/custom_cupertino_route.dart';
4343
import '../main_screen.dart';
4444

4545
class LoginByPasswordScreen extends StatefulWidget {
@@ -65,11 +65,13 @@ class _LoginByPasswordScreenState extends State<LoginByPasswordScreen>
6565
late InputValidateAsyncController _passwordValidateAsyncController;
6666
late InputValidateAsyncController _backupCodeValidateAsyncController;
6767
late PinPutValidateAsyncController _twoFAValidateAsyncController;
68+
late InputValidateAsyncController _emailValidateAsyncController;
6869
String _guestToken = "";
6970
String _flowToken = "";
7071
InitPhase _inited = InitPhase.connecting;
7172
InitPhase _connected = InitPhase.haveNotConnected;
72-
final FocusNode _pinFocusNode = FocusNode();
73+
final FocusNode _twoFAPinFocusNode = FocusNode();
74+
final FocusNode _emailPinFocusNode = FocusNode();
7375
UserInfo? _userInfo;
7476
String errorMessage = "";
7577
bool _isMaximized = false;
@@ -166,6 +168,15 @@ class _LoginByPasswordScreenState extends State<LoginByPasswordScreen>
166168
},
167169
controller: TextEditingController(),
168170
);
171+
_emailValidateAsyncController = InputValidateAsyncController(
172+
validator: (text) async {
173+
if (text.isEmpty) {
174+
return "邮箱代码不能为空";
175+
}
176+
return null;
177+
},
178+
controller: TextEditingController(),
179+
);
169180
initLogin();
170181
}
171182

@@ -212,7 +223,7 @@ class _LoginByPasswordScreenState extends State<LoginByPasswordScreen>
212223
setState(() {
213224
_connected = InitPhase.connecting;
214225
});
215-
controller.animateToPage(5,
226+
controller.animateToPage(6,
216227
duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
217228
await LoginApi.fetchCsrfToken(_guestToken);
218229
await RequestUtil.shareCookie();
@@ -250,7 +261,7 @@ class _LoginByPasswordScreenState extends State<LoginByPasswordScreen>
250261
await LoginApi.checkUsername(_guestToken, _flowToken, account);
251262
if (!res.success || res.data.isEmpty) {
252263
if (res.code == 399) {
253-
_identifierValidateAsyncController.setError("用户不存在");
264+
_identifierValidateAsyncController.setError("用户不存在(${res.message})");
254265
} else {
255266
_identifierValidateAsyncController
256267
.setError("未知错误(code: ${res.code}, message:${res.message})");
@@ -301,7 +312,7 @@ class _LoginByPasswordScreenState extends State<LoginByPasswordScreen>
301312
_guestToken, _flowToken, account);
302313
if (!res.success || res.data.isEmpty) {
303314
if (res.code == 399) {
304-
_alternativeIdentifierValidateAsyncController.setError("用户名验证失败");
315+
_alternativeIdentifierValidateAsyncController.setError("用户名验证失败(${res.message})");
305316
} else {
306317
_alternativeIdentifierValidateAsyncController
307318
.setError("未知错误(code: ${res.code}, message:${res.message})");
@@ -324,7 +335,7 @@ class _LoginByPasswordScreenState extends State<LoginByPasswordScreen>
324335
await LoginApi.checkPassword(_guestToken, _flowToken, password);
325336
if (!res.success || res.data.isEmpty) {
326337
if (res.code == 399) {
327-
_passwordValidateAsyncController.setError("密码错误");
338+
_passwordValidateAsyncController.setError("密码错误(${res.message})");
328339
} else {
329340
_passwordValidateAsyncController
330341
.setError("未知错误(code: ${res.code}, message:${res.message})");
@@ -336,11 +347,16 @@ class _LoginByPasswordScreenState extends State<LoginByPasswordScreen>
336347
}
337348
ILogger.info("Check password", "Get flow_token: $_flowToken");
338349
switch (res.flag as LoginPhase) {
339-
case LoginPhase.check2FA:
350+
case LoginPhase.loginAcid:
340351
controller.animateToPage(3,
341352
duration: const Duration(milliseconds: 300),
342353
curve: Curves.easeInOut);
343354
break;
355+
case LoginPhase.check2FA:
356+
controller.animateToPage(4,
357+
duration: const Duration(milliseconds: 300),
358+
curve: Curves.easeInOut);
359+
break;
344360
case LoginPhase.loginSuccess:
345361
success();
346362
break;
@@ -350,19 +366,39 @@ class _LoginByPasswordScreenState extends State<LoginByPasswordScreen>
350366
}
351367
break;
352368
case 3:
369+
bool valid = (await _emailValidateAsyncController.validate()) == null;
370+
if (!valid) return;
371+
String pin = _emailValidateAsyncController.controller.text;
372+
CustomLoadingDialog.showLoading(title: "验证邮箱代码中...");
373+
var res = await LoginApi.checkEmail(_guestToken, _flowToken, pin);
374+
if (!res.success || res.data.isEmpty) {
375+
if (res.code == 399) {
376+
_emailValidateAsyncController.setError("邮箱代码错误(${res.message})");
377+
} else {
378+
_emailValidateAsyncController
379+
.setError("未知错误(code: ${res.code}, message:${res.message})");
380+
}
381+
_emailPinFocusNode.requestFocus();
382+
} else {
383+
_flowToken = res.data;
384+
ILogger.info("Check email", "Get flow_token: $_flowToken");
385+
success();
386+
}
387+
break;
388+
case 4:
353389
bool valid = (await _twoFAValidateAsyncController.validate()) == null;
354390
if (!valid) return;
355391
String pin = _twoFAValidateAsyncController.controller.text;
356392
CustomLoadingDialog.showLoading(title: "验证一次性代码中...");
357393
var res = await LoginApi.check2FA(_guestToken, _flowToken, pin);
358394
if (!res.success || res.data.isEmpty) {
359395
if (res.code == 399) {
360-
_twoFAValidateAsyncController.setError("一次性代码错误");
396+
_twoFAValidateAsyncController.setError("一次性代码错误(${res.message})");
361397
} else {
362398
_twoFAValidateAsyncController
363399
.setError("未知错误(code: ${res.code}, message:${res.message})");
364400
}
365-
_pinFocusNode.requestFocus();
401+
_twoFAPinFocusNode.requestFocus();
366402
} else {
367403
_flowToken = res.data;
368404
ILogger.info("Check 2FA", "Get flow_token: $_flowToken");
@@ -555,6 +591,30 @@ class _LoginByPasswordScreenState extends State<LoginByPasswordScreen>
555591
),
556592
);
557593
case 3:
594+
return _buildPhasePage(
595+
title: "输入你的邮箱代码",
596+
message: "在下方输入你的邮箱代码",
597+
body: ItemBuilder.buildContainerItem(
598+
context: context,
599+
topRadius: true,
600+
bottomRadius: true,
601+
padding: const EdgeInsets.symmetric(vertical: 10),
602+
child: InputItem(
603+
hint: "输入邮箱代码",
604+
textInputAction: TextInputAction.done,
605+
tailingType: InputItemTailingType.clear,
606+
leadingIcon: Icons.email_outlined,
607+
leadingType: InputItemLeadingType.icon,
608+
keyboardType: TextInputType.text,
609+
validateAsyncController:
610+
_emailValidateAsyncController,
611+
onSubmit: (_) {
612+
_login();
613+
},
614+
),
615+
),
616+
);
617+
case 4:
558618
return _buildPhasePage(
559619
title: "输入你的验证码",
560620
message: "使用代码生成器应用生成一个代码并在下方输入",
@@ -569,29 +629,35 @@ class _LoginByPasswordScreenState extends State<LoginByPasswordScreen>
569629
onCompleted: (_) {
570630
_login();
571631
},
572-
pinFocusNode: _pinFocusNode,
632+
pinFocusNode: _twoFAPinFocusNode,
573633
),
574634
),
575635
);
576-
case 4:
636+
case 5:
577637
return _buildPhasePage(
578638
title: "输入你的备用码",
579639
message: "在下方输入你的备用码",
580-
body: InputItem(
581-
hint: "输入备用码",
582-
textInputAction: TextInputAction.done,
583-
tailingType: InputItemTailingType.clear,
584-
leadingIcon: Icons.backup_outlined,
585-
leadingType: InputItemLeadingType.icon,
586-
keyboardType: TextInputType.text,
587-
validateAsyncController:
588-
_backupCodeValidateAsyncController,
589-
onSubmit: (_) {
590-
_login();
591-
},
640+
body: ItemBuilder.buildContainerItem(
641+
context: context,
642+
topRadius: true,
643+
bottomRadius: true,
644+
padding: const EdgeInsets.symmetric(vertical: 10),
645+
child: InputItem(
646+
hint: "输入备用码",
647+
textInputAction: TextInputAction.done,
648+
tailingType: InputItemTailingType.clear,
649+
leadingIcon: Icons.backup_outlined,
650+
leadingType: InputItemLeadingType.icon,
651+
keyboardType: TextInputType.text,
652+
validateAsyncController:
653+
_backupCodeValidateAsyncController,
654+
onSubmit: (_) {
655+
_login();
656+
},
657+
),
592658
),
593659
);
594-
case 5:
660+
case 6:
595661
return _buildPhasePage(
596662
title: "登录成功",
597663
message: "正在准备连接至Twitee...",

lib/Utils/request_util.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ class RequestUtil {
196196
} on DioException catch (e) {
197197
_printError(e);
198198
_preProcessResponse(e.response);
199-
rethrow;
199+
return e.response;
200200
}
201201
return response;
202202
}
@@ -211,6 +211,7 @@ class RequestUtil {
211211
options.headers?.addAll({
212212
"Authorization": RequestHeaderUtil.defaultAuthentication,
213213
"User-Agent": RequestHeaderUtil.defaultUA,
214+
"x-twitter-client-language": "zh-cn",
214215
});
215216
if (domainType != DomainType.api || forceCsrfToken) {
216217
options.headers?.addAll({

0 commit comments

Comments
 (0)