forked from baron/baron-sso
링크로 로그인하기 구현 완료
This commit is contained in:
@@ -46,6 +46,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
bool _lastLinkIsEmail = true;
|
||||
int _linkResendSeconds = 0;
|
||||
Timer? _linkResendTimer;
|
||||
int _linkExpireSeconds = 0;
|
||||
Timer? _linkExpireTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -120,6 +122,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
_linkResendTimer?.cancel();
|
||||
_linkResendTimer = null;
|
||||
_linkResendSeconds = 0;
|
||||
_linkExpireTimer?.cancel();
|
||||
_linkExpireTimer = null;
|
||||
_linkExpireSeconds = 0;
|
||||
_shortCodePrefixController.clear();
|
||||
_shortCodeDigitsController.clear();
|
||||
}
|
||||
@@ -139,6 +144,25 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
});
|
||||
}
|
||||
|
||||
void _startLinkExpireTimer(int seconds) {
|
||||
_linkExpireSeconds = seconds;
|
||||
_linkExpireTimer?.cancel();
|
||||
_linkExpireTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (!mounted) return;
|
||||
if (_linkExpireSeconds > 0) {
|
||||
setState(() {
|
||||
_linkExpireSeconds--;
|
||||
});
|
||||
return;
|
||||
}
|
||||
timer.cancel();
|
||||
if (mounted) {
|
||||
setState(_resetLinkLoginState);
|
||||
context.go('/signin');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper to decode JWT and get loginId
|
||||
String _getLoginIdFromJwt(String jwt) {
|
||||
try {
|
||||
@@ -505,7 +529,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startEnchantedFlow(String loginId, {required bool isEmail}) async {
|
||||
Future<void> _startEnchantedFlow(String loginId, {required bool isEmail, bool codeOnly = false}) async {
|
||||
try {
|
||||
if (mounted) {
|
||||
showDialog(
|
||||
@@ -516,12 +540,16 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
}
|
||||
|
||||
// 1. Init via Backend API
|
||||
final initResponse = await AuthProxyService.initEnchantedLink(loginId);
|
||||
final initResponse = await AuthProxyService.initEnchantedLink(
|
||||
loginId,
|
||||
codeOnly: codeOnly,
|
||||
);
|
||||
final pendingRef = initResponse['pendingRef'];
|
||||
final mode = (initResponse['mode'] ?? '').toString();
|
||||
final provider = (initResponse['provider'] ?? '').toString();
|
||||
final interval = initResponse['interval'];
|
||||
final resendAfter = initResponse['resendAfter'];
|
||||
final expiresIn = initResponse['expiresIn'];
|
||||
debugPrint("[Auth] Link Sent. PendingRef: $pendingRef, Mode: $mode, Provider: $provider");
|
||||
|
||||
if (mounted) {
|
||||
@@ -543,6 +571,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
if (resendAfter is int && resendAfter > 0) {
|
||||
_startLinkResendTimer(resendAfter);
|
||||
}
|
||||
if (expiresIn is int && expiresIn > 0) {
|
||||
_startLinkExpireTimer(expiresIn);
|
||||
}
|
||||
_pollForSession(pendingRef, initialInterval: initialInterval);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -890,9 +921,12 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
child: TextField(
|
||||
controller: _shortCodeDigitsController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
labelText: "000000",
|
||||
border: OutlineInputBorder(),
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: _linkExpireSeconds > 0
|
||||
? "유효시간 ${_formatTime(_linkExpireSeconds)}"
|
||||
: "000000",
|
||||
),
|
||||
maxLength: 6,
|
||||
),
|
||||
@@ -917,22 +951,50 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextButton(
|
||||
onPressed: _linkResendSeconds > 0
|
||||
? null
|
||||
: () {
|
||||
final loginId = _lastLinkLoginId ?? _linkIdController.text.trim();
|
||||
if (loginId.isEmpty) {
|
||||
_showError("이메일 또는 휴대폰 번호를 입력해 주세요.");
|
||||
return;
|
||||
}
|
||||
_startEnchantedFlow(loginId, isEmail: _lastLinkIsEmail || loginId.contains('@'));
|
||||
},
|
||||
onPressed: () {
|
||||
if (_linkResendSeconds > 0) {
|
||||
_showInfo("재발송은 ${_formatTime(_linkResendSeconds)} 후 가능합니다.");
|
||||
return;
|
||||
}
|
||||
final loginId = _lastLinkLoginId ?? _linkIdController.text.trim();
|
||||
if (loginId.isEmpty) {
|
||||
_showError("이메일 또는 휴대폰 번호를 입력해 주세요.");
|
||||
return;
|
||||
}
|
||||
_startEnchantedFlow(
|
||||
loginId,
|
||||
isEmail: _lastLinkIsEmail || loginId.contains('@'),
|
||||
codeOnly: false,
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
_linkResendSeconds > 0
|
||||
? "재발송 (${_formatTime(_linkResendSeconds)})"
|
||||
: "재발송",
|
||||
),
|
||||
),
|
||||
if (!_lastLinkIsEmail) ...[
|
||||
const SizedBox(height: 4),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (_linkResendSeconds > 0) {
|
||||
_showInfo("재발송은 ${_formatTime(_linkResendSeconds)} 후 가능합니다.");
|
||||
return;
|
||||
}
|
||||
final loginId = _lastLinkLoginId ?? _linkIdController.text.trim();
|
||||
if (loginId.isEmpty) {
|
||||
_showError("휴대폰 번호를 입력해 주세요.");
|
||||
return;
|
||||
}
|
||||
_startEnchantedFlow(
|
||||
loginId,
|
||||
isEmail: false,
|
||||
codeOnly: true,
|
||||
);
|
||||
},
|
||||
child: const Text("코드만 받기(${_formatTime(_linkResendSeconds)})"),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user