Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d5986b81a | ||
|
|
31e3306997 |
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.1.8] - 2026-01-04
|
||||
|
||||
### Changed
|
||||
- Dark background (`#1a1a2e`) on web/index.html to prevent white flash during auth redirects
|
||||
|
||||
## [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
|
||||
|
||||
@@ -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<void> _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),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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.8+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.10.4
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
|
||||
<title>Tatlock</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #1a1a2e;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
|
||||
Reference in New Issue
Block a user