🧱 OpenID Connect client infra
This commit is contained in:
parent
4b9c9aec92
commit
4dbee27718
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -6,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:island/models/auth.dart';
|
import 'package:island/models/auth.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/screens/account/me/settings.dart';
|
import 'package:island/screens/account/me/settings.dart';
|
||||||
|
import 'package:island/screens/auth/oidc.native.dart';
|
||||||
import 'package:island/services/time.dart';
|
import 'package:island/services/time.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
@ -159,6 +161,15 @@ class AccountConnectionNewSheet extends HookConsumerWidget {
|
|||||||
case 'google':
|
case 'google':
|
||||||
case 'github':
|
case 'github':
|
||||||
case 'discord':
|
case 'discord':
|
||||||
|
final token = await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder:
|
||||||
|
(context) => OidcScreen(
|
||||||
|
provider: selectedProvider.value.toLowerCase(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
print(token);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
showSnackBar(context, 'accountConnectionAddError'.tr());
|
showSnackBar(context, 'accountConnectionAddError'.tr());
|
||||||
|
1
lib/screens/auth/oidc.dart
Normal file
1
lib/screens/auth/oidc.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
export 'oidc.native.dart' if (dart.library.html) 'oidc.web.dart';
|
86
lib/screens/auth/oidc.native.dart
Normal file
86
lib/screens/auth/oidc.native.dart
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
|
||||||
|
class OidcScreen extends ConsumerStatefulWidget {
|
||||||
|
final String provider;
|
||||||
|
|
||||||
|
const OidcScreen({super.key, required this.provider});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<OidcScreen> createState() => _OIDCScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OIDCScreenState extends ConsumerState<OidcScreen> {
|
||||||
|
InAppWebViewController? _webViewController;
|
||||||
|
String? authToken;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text('login').tr()),
|
||||||
|
body: InAppWebView(
|
||||||
|
initialUrlRequest: URLRequest(
|
||||||
|
url: WebUri('$serverUrl/auth/login/${widget.provider}'),
|
||||||
|
),
|
||||||
|
onWebViewCreated: (controller) {
|
||||||
|
_webViewController = controller;
|
||||||
|
|
||||||
|
// Register a handler to receive the token from JavaScript
|
||||||
|
controller.addJavaScriptHandler(
|
||||||
|
handlerName: 'tokenHandler',
|
||||||
|
callback: (args) {
|
||||||
|
// args[0] will be the token string
|
||||||
|
if (args.isNotEmpty && args[0] is String) {
|
||||||
|
setState(() {
|
||||||
|
authToken = args[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the token and close the webview
|
||||||
|
Navigator.of(context).pop(authToken);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||||
|
final url = navigationAction.request.url;
|
||||||
|
if (url != null) {
|
||||||
|
final path = url.path;
|
||||||
|
final queryParams = url.queryParameters;
|
||||||
|
|
||||||
|
// Check if we're on the token page
|
||||||
|
if (path.contains('/auth/token') &&
|
||||||
|
queryParams.containsKey('token')) {
|
||||||
|
// Extract token from URL
|
||||||
|
final token = queryParams['token']!;
|
||||||
|
|
||||||
|
// Return the token and close the webview
|
||||||
|
Navigator.of(context).pop(token);
|
||||||
|
return NavigationActionPolicy.CANCEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NavigationActionPolicy.ALLOW;
|
||||||
|
},
|
||||||
|
onLoadStop: (controller, url) async {
|
||||||
|
if (url != null && url.path.contains('/auth/token')) {
|
||||||
|
// Inject JavaScript to call our handler with the token
|
||||||
|
await controller.evaluateJavascript(
|
||||||
|
source: '''
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const token = urlParams.get('token');
|
||||||
|
if (token) {
|
||||||
|
window.flutter_inappwebview.callHandler('tokenHandler', token);
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
78
lib/screens/auth/oidc.web.dart
Normal file
78
lib/screens/auth/oidc.web.dart
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// ignore_for_file: invalid_runtime_check_with_js_interop_types
|
||||||
|
|
||||||
|
import 'dart:ui_web' as ui;
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:web/web.dart' as web;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class OidcScreen extends ConsumerStatefulWidget {
|
||||||
|
final String provider;
|
||||||
|
|
||||||
|
const OidcScreen({super.key, required this.provider});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<OidcScreen> createState() => _OIDCScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OIDCScreenState extends ConsumerState<OidcScreen> {
|
||||||
|
bool _isInitialized = false;
|
||||||
|
final String _viewType = 'oidc-iframe';
|
||||||
|
|
||||||
|
void _setupWebListener(String serverUrl) {
|
||||||
|
// Listen for messages from the iframe
|
||||||
|
web.window.onMessage.listen((event) {
|
||||||
|
if (event.data != null && event.data is String) {
|
||||||
|
final message = event.data as String;
|
||||||
|
if (message.startsWith("token=")) {
|
||||||
|
String token = message.replaceFirst("token=", "");
|
||||||
|
// Return the token and close the screen
|
||||||
|
if (context.mounted) Navigator.pop(context, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the iframe for the OIDC login
|
||||||
|
final iframe =
|
||||||
|
web.HTMLIFrameElement()
|
||||||
|
..src = '$serverUrl/auth/login/${widget.provider}'
|
||||||
|
..style.border = 'none'
|
||||||
|
..width = '100%'
|
||||||
|
..height = '100%';
|
||||||
|
|
||||||
|
// Add the iframe to the document body
|
||||||
|
web.document.body!.append(iframe);
|
||||||
|
|
||||||
|
// Register the iframe as a platform view
|
||||||
|
ui.platformViewRegistry.registerViewFactory(
|
||||||
|
_viewType,
|
||||||
|
(int viewId) => iframe,
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isInitialized = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
Future.delayed(Duration.zero, () {
|
||||||
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
|
_setupWebListener(serverUrl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text('login').tr()),
|
||||||
|
body:
|
||||||
|
_isInitialized
|
||||||
|
? HtmlElementView(viewType: _viewType)
|
||||||
|
: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user