diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b9e87..cc6d484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.7] - 2026-01-04 + +### Removed +- Removed `/callback` route from Flutter router - AuthController handles callback in main() before app starts +- Removed `_OidcCallbackPage` widget - no visible auth UI needed + ## [1.1.6] - 2026-01-04 ### Changed diff --git a/lib/routing/app_router.dart b/lib/routing/app_router.dart index 3056114..abed9e5 100644 --- a/lib/routing/app_router.dart +++ b/lib/routing/app_router.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tatlock_ui/core/auth/auth_provider.dart'; import 'package:tatlock_ui/features/control_room/router.dart'; import 'package:tatlock_ui/features/front_hall/presentation/pages/front_hall_page.dart'; import 'package:tatlock_ui/features/security/router.dart'; @@ -15,7 +13,6 @@ abstract class AppRoutes { static const frontHall = '/'; static const parlor = '/parlor'; static const settings = '/settings'; - static const callback = '/callback'; } /// Provides the GoRouter instance. @@ -25,17 +22,6 @@ GoRouter appRouter(Ref ref) { initialLocation: AppRoutes.frontHall, debugLogDiagnostics: true, routes: [ - // OIDC callback route (handles auth code exchange) - GoRoute( - path: AppRoutes.callback, - name: 'callback', - builder: (context, state) => _OidcCallbackPage( - code: state.uri.queryParameters['code'], - callbackState: state.uri.queryParameters['state'], - error: state.uri.queryParameters['error'], - errorDescription: state.uri.queryParameters['error_description'], - ), - ), // Main app routes (inside shell with app scaffold) ShellRoute( builder: (context, state, child) => AppScaffold(child: child), @@ -100,148 +86,3 @@ class _PlaceholderPage extends StatelessWidget { } } -/// OIDC callback page that handles the authorization code exchange. -class _OidcCallbackPage extends ConsumerStatefulWidget { - const _OidcCallbackPage({ - this.code, - this.callbackState, - this.error, - this.errorDescription, - }); - - final String? code; - final String? callbackState; - final String? error; - final String? errorDescription; - - @override - ConsumerState<_OidcCallbackPage> createState() => _OidcCallbackPageState(); -} - -class _OidcCallbackPageState extends ConsumerState<_OidcCallbackPage> { - bool _isProcessing = true; - String? _error; - - @override - void initState() { - super.initState(); - // Defer callback processing to avoid Riverpod state modification during build - WidgetsBinding.instance.addPostFrameCallback((_) { - _processCallback(); - }); - } - - Future _processCallback() async { - // Check mounted before any async work - if (!mounted) return; - - // Check for error from Authentik - if (widget.error != null) { - // Silent OIDC (prompt=none) failed - no existing session - // Fall back to regular OIDC flow to show login UI - if (widget.error == 'login_required') { - ref.read(authProvider.notifier).signIn(); - return; - } - - setState(() { - _isProcessing = false; - _error = widget.errorDescription ?? widget.error; - }); - return; - } - - // Check for required parameters - if (widget.code == null || widget.callbackState == null) { - setState(() { - _isProcessing = false; - _error = 'Invalid callback - missing code or state parameter'; - }); - return; - } - - // Get notifier reference before async gap to avoid disposed ref errors - final authNotifier = ref.read(authProvider.notifier); - - // Exchange code for tokens - try { - await authNotifier.handleOidcCallback( - widget.code!, - widget.callbackState!, - ); - - // Navigate to home on success - if (mounted) { - context.go(AppRoutes.frontHall); - } - } catch (e) { - if (mounted) { - setState(() { - _isProcessing = false; - _error = e.toString(); - }); - } - } - } - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - - return Scaffold( - body: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 400), - child: Card( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - _error != null ? Icons.error_outline : Icons.home_work_outlined, - size: 64, - color: _error != null ? colorScheme.error : colorScheme.primary, - ), - const SizedBox(height: 24), - Text( - _error != null ? 'Authentication Failed' : 'Signing in...', - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 16), - if (_isProcessing) - const CircularProgressIndicator() - else if (_error != null) ...[ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: colorScheme.errorContainer, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - _error!, - style: TextStyle(color: colorScheme.onErrorContainer), - textAlign: TextAlign.center, - ), - ), - const SizedBox(height: 16), - OutlinedButton.icon( - onPressed: () => context.go(AppRoutes.frontHall), - icon: const Icon(Icons.refresh), - label: const Text('Try again'), - style: OutlinedButton.styleFrom( - minimumSize: const Size(double.infinity, 48), - ), - ), - ], - ], - ), - ), - ), - ), - ), - ); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index b113021..cad6d62 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.1.6+1 +version: 1.1.7+1 environment: sdk: ^3.10.4