mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-12-24 14:32:37 +01:00
feat: Implement sso
This commit is contained in:
parent
6a0e36ab38
commit
d1d470deb6
@ -10,6 +10,7 @@ abstract class AppConfig {
|
|||||||
static const bool enableRegistration = true;
|
static const bool enableRegistration = true;
|
||||||
static String _privacyUrl = 'https://fluffychat.im/en/privacy.html';
|
static String _privacyUrl = 'https://fluffychat.im/en/privacy.html';
|
||||||
static String get privacyUrl => _privacyUrl;
|
static String get privacyUrl => _privacyUrl;
|
||||||
|
static const String appId = 'im.fluffychat.FluffyChat';
|
||||||
static const String sourceCodeUrl = 'https://gitlab.com/famedly/fluffychat';
|
static const String sourceCodeUrl = 'https://gitlab.com/famedly/fluffychat';
|
||||||
static const String supportUrl =
|
static const String supportUrl =
|
||||||
'https://gitlab.com/famedly/fluffychat/issues';
|
'https://gitlab.com/famedly/fluffychat/issues';
|
||||||
|
@ -156,8 +156,13 @@ class MatrixState extends State<Matrix> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
Logs().w('Warning! Cannot handle the stage "$stage"');
|
await widget.apl.currentState.pushNamed(
|
||||||
return;
|
'/authwebview/$stage/${uiaRequest.session}',
|
||||||
|
arguments: () => null,
|
||||||
|
);
|
||||||
|
return uiaRequest.completeStage(
|
||||||
|
AuthenticationData(session: uiaRequest.session),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import 'package:fluffychat/views/settings_notifications.dart';
|
|||||||
import 'package:fluffychat/views/settings_style.dart';
|
import 'package:fluffychat/views/settings_style.dart';
|
||||||
import 'package:fluffychat/views/sign_up.dart';
|
import 'package:fluffychat/views/sign_up.dart';
|
||||||
import 'package:fluffychat/views/sign_up_password.dart';
|
import 'package:fluffychat/views/sign_up_password.dart';
|
||||||
|
import 'package:fluffychat/views/sso_web_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class FluffyRoutes {
|
class FluffyRoutes {
|
||||||
@ -47,6 +48,8 @@ class FluffyRoutes {
|
|||||||
return ViewData(mainView: (_) => HomeserverPicker());
|
return ViewData(mainView: (_) => HomeserverPicker());
|
||||||
case 'login':
|
case 'login':
|
||||||
return ViewData(mainView: (_) => Login());
|
return ViewData(mainView: (_) => Login());
|
||||||
|
case 'sso':
|
||||||
|
return ViewData(mainView: (_) => SsoWebView());
|
||||||
case 'signup':
|
case 'signup':
|
||||||
if (parts.length == 5 && parts[2] == 'password') {
|
if (parts.length == 5 && parts[2] == 'password') {
|
||||||
return ViewData(
|
return ViewData(
|
||||||
@ -129,6 +132,17 @@ class FluffyRoutes {
|
|||||||
mainView: (_) => Archive(),
|
mainView: (_) => Archive(),
|
||||||
emptyView: (_) => EmptyPage(),
|
emptyView: (_) => EmptyPage(),
|
||||||
);
|
);
|
||||||
|
case 'authwebview':
|
||||||
|
if (parts.length == 4) {
|
||||||
|
return ViewData(
|
||||||
|
mainView: (_) => AuthWebView(
|
||||||
|
parts[2],
|
||||||
|
Uri.decodeComponent(parts[3]),
|
||||||
|
settings.arguments,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'discover':
|
case 'discover':
|
||||||
return ViewData(
|
return ViewData(
|
||||||
mainView: (_) =>
|
mainView: (_) =>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
|
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
|
||||||
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:fluffychat/components/matrix.dart';
|
import 'package:fluffychat/components/matrix.dart';
|
||||||
import 'package:fluffychat/app_config.dart';
|
import 'package:fluffychat/app_config.dart';
|
||||||
import 'package:fluffychat/components/sentry_switch_list_tile.dart';
|
import 'package:fluffychat/components/sentry_switch_list_tile.dart';
|
||||||
@ -30,10 +31,32 @@ class _HomeserverPickerState extends State<HomeserverPicker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
|
|
||||||
|
// Look up well known
|
||||||
try {
|
try {
|
||||||
await Matrix.of(context).client.checkHomeserver(homeserver);
|
final wellKnown = await MatrixApi(homeserver: Uri.parse(homeserver))
|
||||||
await AdaptivePageLayout.of(context)
|
.requestWellKnownInformations();
|
||||||
.pushNamed(AppConfig.enableRegistration ? '/signup' : '/login');
|
homeserver = wellKnown.mHomeserver.baseUrl;
|
||||||
|
} catch (e) {
|
||||||
|
Logs().v('Found no well known information', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Matrix.of(context)
|
||||||
|
.client
|
||||||
|
.checkHomeserver(homeserver, supportedLoginTypes: {
|
||||||
|
AuthenticationTypes.password,
|
||||||
|
if (PlatformInfos.isMobile) AuthenticationTypes.sso
|
||||||
|
});
|
||||||
|
final loginTypes = await Matrix.of(context).client.requestLoginTypes();
|
||||||
|
if (loginTypes.flows
|
||||||
|
.any((flow) => flow.type == AuthenticationTypes.password)) {
|
||||||
|
await AdaptivePageLayout.of(context)
|
||||||
|
.pushNamed(AppConfig.enableRegistration ? '/signup' : '/login');
|
||||||
|
} else if (loginTypes.flows
|
||||||
|
.any((flow) => flow.type == AuthenticationTypes.sso)) {
|
||||||
|
await AdaptivePageLayout.of(context).pushNamed('/sso');
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore: unawaited_futures
|
// ignore: unawaited_futures
|
||||||
FlushbarHelper.createError(
|
FlushbarHelper.createError(
|
||||||
|
@ -22,6 +22,9 @@ class DevicesSettingsState extends State<DevicesSettings> {
|
|||||||
|
|
||||||
void reload() => setState(() => devices = null);
|
void reload() => setState(() => devices = null);
|
||||||
|
|
||||||
|
bool _loadingDeletingDevices = false;
|
||||||
|
String _errorDeletingDevices;
|
||||||
|
|
||||||
void _removeDevicesAction(BuildContext context, List<Device> devices) async {
|
void _removeDevicesAction(BuildContext context, List<Device> devices) async {
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -33,33 +36,24 @@ class DevicesSettingsState extends State<DevicesSettings> {
|
|||||||
for (var userDevice in devices) {
|
for (var userDevice in devices) {
|
||||||
deviceIds.add(userDevice.deviceId);
|
deviceIds.add(userDevice.deviceId);
|
||||||
}
|
}
|
||||||
final password = await showTextInputDialog(
|
|
||||||
title: L10n.of(context).pleaseEnterYourPassword,
|
|
||||||
context: context,
|
|
||||||
textFields: [
|
|
||||||
DialogTextField(
|
|
||||||
hintText: '******',
|
|
||||||
obscureText: true,
|
|
||||||
minLines: 1,
|
|
||||||
maxLines: 1,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if (password == null) return;
|
|
||||||
|
|
||||||
final success = await showFutureLoadingDialog(
|
try {
|
||||||
context: context,
|
setState(() {
|
||||||
future: () => matrix.client.deleteDevices(
|
_loadingDeletingDevices = true;
|
||||||
deviceIds,
|
_errorDeletingDevices = null;
|
||||||
auth: AuthenticationPassword(
|
});
|
||||||
password: password.single,
|
await matrix.client.uiaRequestBackground(
|
||||||
user: matrix.client.userID,
|
(auth) => matrix.client.deleteDevices(
|
||||||
identifier: AuthenticationUserIdentifier(user: matrix.client.userID),
|
deviceIds,
|
||||||
|
auth: auth,
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
if (success.error == null) {
|
|
||||||
reload();
|
reload();
|
||||||
|
} catch (e, s) {
|
||||||
|
Logs().v('Error while deleting devices', e, s);
|
||||||
|
setState(() => _errorDeletingDevices = e.toString());
|
||||||
|
} finally {
|
||||||
|
setState(() => _loadingDeletingDevices = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,11 +121,16 @@ class DevicesSettingsState extends State<DevicesSettings> {
|
|||||||
if (devices.isNotEmpty)
|
if (devices.isNotEmpty)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10n.of(context).removeAllOtherDevices,
|
_errorDeletingDevices ??
|
||||||
|
L10n.of(context).removeAllOtherDevices,
|
||||||
style: TextStyle(color: Colors.red),
|
style: TextStyle(color: Colors.red),
|
||||||
),
|
),
|
||||||
trailing: Icon(Icons.delete_outline),
|
trailing: _loadingDeletingDevices
|
||||||
onTap: () => _removeDevicesAction(context, devices),
|
? CircularProgressIndicator()
|
||||||
|
: Icon(Icons.delete_outline),
|
||||||
|
onTap: _loadingDeletingDevices
|
||||||
|
? null
|
||||||
|
: () => _removeDevicesAction(context, devices),
|
||||||
),
|
),
|
||||||
Divider(height: 1),
|
Divider(height: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
60
lib/views/sso_web_view.dart
Normal file
60
lib/views/sso_web_view.dart
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
|
import 'package:fluffychat/components/matrix.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
|
import '../app_config.dart';
|
||||||
|
|
||||||
|
class SsoWebView extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_SsoWebViewState createState() => _SsoWebViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SsoWebViewState extends State<SsoWebView> {
|
||||||
|
bool _loading = false;
|
||||||
|
String _error;
|
||||||
|
|
||||||
|
void _login(BuildContext context, String token) async {
|
||||||
|
setState(() => _loading = true);
|
||||||
|
try {
|
||||||
|
await Matrix.of(context).client.login(
|
||||||
|
type: AuthenticationTypes.token,
|
||||||
|
userIdentifierType: null,
|
||||||
|
token: token,
|
||||||
|
initialDeviceDisplayName: Matrix.of(context).clientName,
|
||||||
|
);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logs().e('Login with token failed', e, s);
|
||||||
|
setState(() => _error = e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final url =
|
||||||
|
'${Matrix.of(context).client.homeserver?.toString()}/_matrix/client/r0/login/sso/redirect?redirectUrl=${Uri.encodeQueryComponent(AppConfig.appId.toLowerCase() + '://sso')}';
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
L10n.of(context).logInTo(Matrix.of(context).client.homeserver?.host ??
|
||||||
|
L10n.of(context).oopsSomethingWentWrong),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: _error != null
|
||||||
|
? Center(child: Text(_error))
|
||||||
|
: _loading
|
||||||
|
? Center(child: CircularProgressIndicator())
|
||||||
|
: WebView(
|
||||||
|
initialUrl: url,
|
||||||
|
javascriptMode: JavascriptMode.unrestricted,
|
||||||
|
onPageStarted: (url) {
|
||||||
|
if (url.startsWith(AppConfig.appId.toLowerCase())) {
|
||||||
|
_login(context,
|
||||||
|
Uri.parse(url).queryParameters['loginToken']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user