1
0
forked from baron/baron-sso
Files
baron-sso/userfront/lib/features/auth/presentation/approve_qr_screen.dart
2026-01-29 16:35:08 +09:00

163 lines
5.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:descope/descope.dart';
import 'package:go_router/go_router.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;
@override
void initState() {
super.initState();
_bootstrapCookieSession();
}
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);
}
}
}
Future<void> _handleApprove() async {
if (widget.pendingRef == null) return;
final storedToken = AuthTokenStore.getToken();
final session = Descope.sessionManager.session;
final usesCookie = AuthTokenStore.usesCookie();
var hasCookie = usesCookie;
if (storedToken == null && (session == null || session.refreshToken.isExpired) && !hasCookie) {
hasCookie = await _bootstrapCookieSession();
}
if (storedToken == null && (session == null || session.refreshToken.isExpired) && !hasCookie) {
setState(() => _message = "Please log in on your phone first.");
context.go('/signin'); // Redirect to login
return;
}
setState(() {
_isLoading = true;
_message = null;
});
// jwt 유효성 확인
try {
final token = storedToken ?? session?.sessionToken.jwt ?? '';
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('/');
});
} catch (e) {
setState(() => _message = "Error: $e");
} finally {
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
final hasStoredToken = AuthTokenStore.getToken() != null;
final hasDescopeSession = Descope.sessionManager.session?.refreshToken.isExpired == false;
final usesCookie = AuthTokenStore.usesCookie();
final isLoggedIn = hasStoredToken || hasDescopeSession || usesCookie || _isCheckingSession;
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('/signin'),
child: const Text("Login on this device first"),
),
),
if (_success)
FilledButton(
onPressed: () => context.go('/'),
child: const Text("Go to My Dashboard"),
),
],
),
),
),
);
}
}