1
0
forked from baron/baron-sso
Files
baron-sso/userfront/lib/features/auth/presentation/approve_qr_screen.dart

187 lines
5.7 KiB
Dart

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';
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;
@override
void initState() {
super.initState();
_bootstrapCookieSession().then((_) => _redirectIfNotLoggedIn());
}
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);
context.go('$target?notice=qr_login_required');
});
}
}
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);
context.go('$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();
}
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 (!_success)
FilledButton.icon(
onPressed: _isLoading || !isLoggedIn ? null : _handleApprove,
icon: const Icon(Icons.check_circle),
label: const Text("Approve Login"),
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(60),
backgroundColor: Colors.blue,
),
),
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)
FilledButton(
onPressed: () => context.go(buildLocalizedHomePath(Uri.base)),
child: const Text("Go to My Dashboard"),
),
],
),
),
),
);
}
}