첫 커밋: 로컬 프로젝트 업로드
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../../core/i18n/locale_utils.dart';
|
||||
import '../../../../core/services/auth_proxy_service.dart';
|
||||
import '../../../../core/services/auth_token_store.dart';
|
||||
import '../../../../core/services/web_window.dart';
|
||||
|
||||
class ApproveQrScreen extends StatefulWidget {
|
||||
final String? pendingRef;
|
||||
const ApproveQrScreen({super.key, this.pendingRef});
|
||||
|
||||
@override
|
||||
State<ApproveQrScreen> createState() => _ApproveQrScreenState();
|
||||
}
|
||||
|
||||
class _ApproveQrScreenState extends State<ApproveQrScreen> {
|
||||
bool _isLoading = false;
|
||||
String? _message;
|
||||
bool _success = false;
|
||||
bool _isCheckingSession = false;
|
||||
bool _redirectingToLogin = false;
|
||||
bool _autoApproveTriggered = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bootstrapCookieSession().then((_) {
|
||||
_redirectIfNotLoggedIn();
|
||||
_maybeAutoApprove();
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> _bootstrapCookieSession() async {
|
||||
if (AuthTokenStore.usesCookie()) {
|
||||
return true;
|
||||
}
|
||||
if (_isCheckingSession) {
|
||||
return false;
|
||||
}
|
||||
setState(() => _isCheckingSession = true);
|
||||
try {
|
||||
await AuthProxyService.checkCookieSession();
|
||||
AuthTokenStore.setCookieMode(provider: 'ory');
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isCheckingSession = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _redirectIfNotLoggedIn() {
|
||||
if (_redirectingToLogin || !mounted) return;
|
||||
final hasStoredToken = AuthTokenStore.getToken()?.isNotEmpty ?? false;
|
||||
final usesCookie = AuthTokenStore.usesCookie();
|
||||
final isLoggedIn = hasStoredToken || usesCookie;
|
||||
if (!isLoggedIn) {
|
||||
_redirectingToLogin = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
final target = buildLocalizedSigninPath(Uri.base);
|
||||
webWindow.redirectTo('$target?notice=qr_login_required');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _maybeAutoApprove() {
|
||||
if (!mounted || _autoApproveTriggered) return;
|
||||
if (widget.pendingRef == null || widget.pendingRef!.trim().isEmpty) {
|
||||
if (_message == null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_message = 'Error: pendingRef is missing.';
|
||||
});
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final hasStoredToken = AuthTokenStore.getToken()?.isNotEmpty ?? false;
|
||||
final usesCookie = AuthTokenStore.usesCookie();
|
||||
final isLoggedIn = hasStoredToken || usesCookie || _isCheckingSession;
|
||||
if (!isLoggedIn || _isLoading || _success) {
|
||||
return;
|
||||
}
|
||||
|
||||
_autoApproveTriggered = true;
|
||||
_handleApprove();
|
||||
}
|
||||
|
||||
Future<void> _handleApprove() async {
|
||||
if (widget.pendingRef == null) return;
|
||||
|
||||
final storedToken = AuthTokenStore.getToken();
|
||||
final usesCookie = AuthTokenStore.usesCookie();
|
||||
var hasCookie = usesCookie;
|
||||
if (storedToken == null && !hasCookie) {
|
||||
hasCookie = await _bootstrapCookieSession();
|
||||
}
|
||||
if (storedToken == null && !hasCookie) {
|
||||
if (mounted) {
|
||||
final target = buildLocalizedSigninPath(Uri.base);
|
||||
webWindow.redirectTo('$target?notice=qr_login_required');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_message = null;
|
||||
});
|
||||
// jwt 유효성 확인
|
||||
try {
|
||||
final token = storedToken ?? '';
|
||||
await AuthProxyService.approveQrLogin(
|
||||
widget.pendingRef!,
|
||||
token: token,
|
||||
withCredentials: hasCookie,
|
||||
);
|
||||
setState(() {
|
||||
_success = true;
|
||||
_message = "Login Approved! Your browser should now be logged in.";
|
||||
});
|
||||
|
||||
// Automatically go to dashboard after a short delay
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
if (mounted) context.go(buildLocalizedHomePath(Uri.base));
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() => _message = "Error: $e");
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasStoredToken = AuthTokenStore.getToken()?.isNotEmpty ?? false;
|
||||
final usesCookie = AuthTokenStore.usesCookie();
|
||||
final isLoggedIn = hasStoredToken || usesCookie || _isCheckingSession;
|
||||
|
||||
if (!isLoggedIn && !_redirectingToLogin) {
|
||||
_redirectIfNotLoggedIn();
|
||||
}
|
||||
if (isLoggedIn && !_success && !_isLoading) {
|
||||
_maybeAutoApprove();
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("QR Login Approval")),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.phonelink_lock, size: 80, color: Colors.blue),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
"Web Login Request",
|
||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
"A computer is trying to log in using this QR code.",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey.shade600),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
if (_message != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Text(
|
||||
_message!,
|
||||
style: TextStyle(
|
||||
color: _success ? Colors.green : Colors.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
|
||||
if (_isLoading)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
|
||||
if (!_success && !_isLoading)
|
||||
Text(
|
||||
"Approving login request automatically...",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey.shade700),
|
||||
),
|
||||
|
||||
if (!isLoggedIn && !_success)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: TextButton(
|
||||
onPressed: () =>
|
||||
context.go(buildLocalizedSigninPath(Uri.base)),
|
||||
child: const Text("Login on this device first"),
|
||||
),
|
||||
),
|
||||
|
||||
if (!_success && !_isLoading && _message != null)
|
||||
FilledButton.icon(
|
||||
onPressed: !isLoggedIn
|
||||
? null
|
||||
: () {
|
||||
_autoApproveTriggered = false;
|
||||
_handleApprove();
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text("Retry Approval"),
|
||||
),
|
||||
|
||||
if (_success)
|
||||
FilledButton(
|
||||
onPressed: () => context.go(buildLocalizedHomePath(Uri.base)),
|
||||
child: const Text("Go to My Dashboard"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user