Merge branch 'krille/sso' into 'main'

feat: Implement sso

Closes #13

See merge request famedly/fluffychat!346
This commit is contained in:
Krille Fear 2021-01-18 22:51:46 +00:00
commit 5292f41d0c
6 changed files with 134 additions and 32 deletions

View File

@ -10,6 +10,7 @@ abstract class AppConfig {
static const bool enableRegistration = true;
static String _privacyUrl = 'https://fluffychat.im/en/privacy.html';
static String get privacyUrl => _privacyUrl;
static const String appId = 'im.fluffychat.FluffyChat';
static const String sourceCodeUrl = 'https://gitlab.com/famedly/fluffychat';
static const String supportUrl =
'https://gitlab.com/famedly/fluffychat/issues';

View File

@ -156,8 +156,13 @@ class MatrixState extends State<Matrix> {
),
);
default:
Logs().w('Warning! Cannot handle the stage "$stage"');
return;
await widget.apl.currentState.pushNamed(
'/authwebview/$stage/${uiaRequest.session}',
arguments: () => null,
);
return uiaRequest.completeStage(
AuthenticationData(session: uiaRequest.session),
);
}
}

View File

@ -27,6 +27,7 @@ import 'package:fluffychat/views/settings_notifications.dart';
import 'package:fluffychat/views/settings_style.dart';
import 'package:fluffychat/views/sign_up.dart';
import 'package:fluffychat/views/sign_up_password.dart';
import 'package:fluffychat/views/sso_web_view.dart';
import 'package:flutter/material.dart';
class FluffyRoutes {
@ -47,6 +48,8 @@ class FluffyRoutes {
return ViewData(mainView: (_) => HomeserverPicker());
case 'login':
return ViewData(mainView: (_) => Login());
case 'sso':
return ViewData(mainView: (_) => SsoWebView());
case 'signup':
if (parts.length == 5 && parts[2] == 'password') {
return ViewData(
@ -129,6 +132,17 @@ class FluffyRoutes {
mainView: (_) => Archive(),
emptyView: (_) => EmptyPage(),
);
case 'authwebview':
if (parts.length == 4) {
return ViewData(
mainView: (_) => AuthWebView(
parts[2],
Uri.decodeComponent(parts[3]),
settings.arguments,
),
);
}
break;
case 'discover':
return ViewData(
mainView: (_) =>

View File

@ -1,6 +1,7 @@
import 'dart:math';
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/matrix.dart';
import 'package:fluffychat/app_config.dart';
import 'package:fluffychat/components/sentry_switch_list_tile.dart';
@ -30,10 +31,32 @@ class _HomeserverPickerState extends State<HomeserverPicker> {
}
setState(() => _isLoading = true);
// Look up well known
try {
await Matrix.of(context).client.checkHomeserver(homeserver);
await AdaptivePageLayout.of(context)
.pushNamed(AppConfig.enableRegistration ? '/signup' : '/login');
final wellKnown = await MatrixApi(homeserver: Uri.parse(homeserver))
.requestWellKnownInformations();
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) {
// ignore: unawaited_futures
FlushbarHelper.createError(

View File

@ -22,6 +22,9 @@ class DevicesSettingsState extends State<DevicesSettings> {
void reload() => setState(() => devices = null);
bool _loadingDeletingDevices = false;
String _errorDeletingDevices;
void _removeDevicesAction(BuildContext context, List<Device> devices) async {
if (await showOkCancelAlertDialog(
context: context,
@ -33,33 +36,24 @@ class DevicesSettingsState extends State<DevicesSettings> {
for (var userDevice in devices) {
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(
context: context,
future: () => matrix.client.deleteDevices(
deviceIds,
auth: AuthenticationPassword(
password: password.single,
user: matrix.client.userID,
identifier: AuthenticationUserIdentifier(user: matrix.client.userID),
try {
setState(() {
_loadingDeletingDevices = true;
_errorDeletingDevices = null;
});
await matrix.client.uiaRequestBackground(
(auth) => matrix.client.deleteDevices(
deviceIds,
auth: auth,
),
),
);
if (success.error == null) {
);
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)
ListTile(
title: Text(
L10n.of(context).removeAllOtherDevices,
_errorDeletingDevices ??
L10n.of(context).removeAllOtherDevices,
style: TextStyle(color: Colors.red),
),
trailing: Icon(Icons.delete_outline),
onTap: () => _removeDevicesAction(context, devices),
trailing: _loadingDeletingDevices
? CircularProgressIndicator()
: Icon(Icons.delete_outline),
onTap: _loadingDeletingDevices
? null
: () => _removeDevicesAction(context, devices),
),
Divider(height: 1),
Expanded(

View 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']);
}
},
),
);
}
}