chore: Follow up matrix native oidc

This commit is contained in:
Christian Kußowski 2026-03-08 10:15:45 +01:00
commit aba8c3faf2
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
13 changed files with 63 additions and 58 deletions

View file

@ -106,7 +106,7 @@
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="im.fluffychat" android:host="login"/> <data android:scheme="chat.fluffy" android:host="login"/>
</intent-filter> </intent-filter>
</activity> </activity>

View file

@ -35,6 +35,7 @@
<array> <array>
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>im.fluffychat</string> <string>im.fluffychat</string>
<string>chat.fluffy</string>
<string>matrix</string> <string>matrix</string>
</array> </array>
</dict> </dict>

View file

@ -21,7 +21,6 @@ abstract class AppConfig {
static const double spaceBorderRadius = 11.0; static const double spaceBorderRadius = 11.0;
static const double columnWidth = 360.0; static const double columnWidth = 360.0;
static const String website = 'https://fluffy.chat';
static const String enablePushTutorial = static const String enablePushTutorial =
'https://fluffy.chat/faq/#push_without_google_services'; 'https://fluffy.chat/faq/#push_without_google_services';
static const String encryptionTutorial = static const String encryptionTutorial =
@ -31,7 +30,7 @@ abstract class AppConfig {
static const String howDoIGetStickersTutorial = static const String howDoIGetStickersTutorial =
'https://fluffy.chat/faq/#how_do_i_get_stickers'; 'https://fluffy.chat/faq/#how_do_i_get_stickers';
static const String appId = 'im.fluffychat.FluffyChat'; static const String appId = 'im.fluffychat.FluffyChat';
static const String appOpenUrlScheme = 'im.fluffychat'; static const String appOpenUrlScheme = 'chat.fluffy';
static const String sourceCodeUrl = static const String sourceCodeUrl =
'https://github.com/krille-chan/fluffychat'; 'https://github.com/krille-chan/fluffychat';
@ -54,12 +53,6 @@ abstract class AppConfig {
path: 'krille-chan/fluffychat/refs/heads/main/recommended_homeservers.json', path: 'krille-chan/fluffychat/refs/heads/main/recommended_homeservers.json',
); );
static final Uri privacyUrl = Uri(
scheme: 'https',
host: 'fluffy.chat',
path: '/en/privacy',
);
static const String mainIsolatePortName = 'main_isolate'; static const String mainIsolatePortName = 'main_isolate';
static const String pushIsolatePortName = 'push_isolate'; static const String pushIsolatePortName = 'push_isolate';
} }

View file

@ -55,7 +55,17 @@ enum AppSettings<T> {
enableSoftLogout<bool>('chat.fluffy.enable_soft_logout', false), enableSoftLogout<bool>('chat.fluffy.enable_soft_logout', false),
enableMatrixNativeOIDC<bool>('chat.fluffy.enable_matrix_native_oidc', false), enableMatrixNativeOIDC<bool>('chat.fluffy.enable_matrix_native_oidc', false),
presetHomeserver<String>('chat.fluffy.preset_homeserver', ''), presetHomeserver<String>('chat.fluffy.preset_homeserver', ''),
welcomeText<String>('chat.fluffy.welcome_text', ''); welcomeText<String>('chat.fluffy.welcome_text', ''),
website<String>('chat.fluffy.website_url', 'https://fluffy.chat'),
logoUrl<String>(
'chat.fluffy.logo_url',
'https://fluffy.chat/assets/favicon.png',
),
privacyPolicy<String>(
'chat.fluffy.privacy_policy_url',
'https://fluffy.chat/en/privacy',
),
tos<String>('chat.fluffy.tos_url', 'https://fluffy.chat/en/tos');
final String key; final String key;
final T defaultValue; final T defaultValue;

View file

@ -2,10 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/intro/flows/restore_backup_flow.dart'; import 'package:fluffychat/pages/intro/flows/restore_backup_flow.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
@ -58,7 +57,7 @@ class IntroPage extends StatelessWidget {
), ),
), ),
PopupMenuItem( PopupMenuItem(
onTap: () => launchUrl(AppConfig.privacyUrl), onTap: () => launchUrlString(AppSettings.privacyPolicy.value),
child: Row( child: Row(
mainAxisSize: .min, mainAxisSize: .min,
children: [ children: [

View file

@ -77,6 +77,7 @@ class _IntroPagePresenterState extends State<IntroPagePresenter> {
final client = await Matrix.of(context).getLoginClient(); final client = await Matrix.of(context).getLoginClient();
await client.checkHomeserver(homeserverUrl); await client.checkHomeserver(homeserverUrl);
await client.oidcLogin(session: session, code: code, state: state); await client.oidcLogin(session: session, code: code, state: state);
if (context.mounted) context.go('/backup');
} catch (e, s) { } catch (e, s) {
Logs().w('Unable to login via OIDC', e, s); Logs().w('Unable to login via OIDC', e, s);
if (mounted) { if (mounted) {

View file

@ -3,8 +3,9 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
@ -200,7 +201,7 @@ class SettingsView extends StatelessWidget {
ListTile( ListTile(
leading: const Icon(Icons.privacy_tip_outlined), leading: const Icon(Icons.privacy_tip_outlined),
title: Text(L10n.of(context).privacy), title: Text(L10n.of(context).privacy),
onTap: () => launchUrl(AppConfig.privacyUrl), onTap: () => launchUrlString(AppSettings.privacyPolicy.value),
), ),
ListTile( ListTile(
leading: const Icon(Icons.info_outline_rounded), leading: const Icon(Icons.info_outline_rounded),

View file

@ -98,19 +98,11 @@ class SignInPage extends StatelessWidget {
final website = server.website; final website = server.website;
return Semantics( return Semantics(
identifier: 'homeserver_tile_$i', identifier: 'homeserver_tile_$i',
child: RadioListTile.adaptive( child: RadioListTile(
value: server, value: server,
enabled: enabled:
state.loginLoading.connectionState != state.loginLoading.connectionState !=
ConnectionState.waiting, ConnectionState.waiting,
radioScaleFactor:
FluffyThemes.isColumnMode(context) ||
{
TargetPlatform.iOS,
TargetPlatform.macOS,
}.contains(theme.platform)
? 2
: 1,
title: Row( title: Row(
children: [ children: [
Expanded( Expanded(

View file

@ -0,0 +1,27 @@
import 'package:flutter/foundation.dart';
import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/platform_infos.dart';
(Uri redirectUrl, String urlScheme) calcRedirectUrl({
bool withAuthHtmlPath = false,
}) {
var redirectUrl = kIsWeb
? Uri.parse(html.window.location.href.split('#').first.split('?').first)
: (PlatformInfos.isMobile || PlatformInfos.isMacOS)
? Uri.parse('${AppConfig.appOpenUrlScheme.toLowerCase()}:/login')
: Uri.parse('http://localhost:3001/login');
if (kIsWeb && withAuthHtmlPath) {
redirectUrl = redirectUrl.resolveUri(Uri(pathSegments: ['auth.html']));
}
final urlScheme =
(PlatformInfos.isMobile || PlatformInfos.isWeb || PlatformInfos.isMacOS)
? redirectUrl.scheme
: 'http://localhost:3001';
return (redirectUrl, urlScheme);
}

View file

@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/setting_keys.dart';
@ -73,6 +74,7 @@ Future<void> connectToHomeserverFlow(
context.go('/backup'); context.go('/backup');
} }
} catch (e, s) { } catch (e, s) {
Logs().w('Unable to login', e, s);
setState(AsyncSnapshot.withError(ConnectionState.done, e, s)); setState(AsyncSnapshot.withError(ConnectionState.done, e, s));
if (!context.mounted) return; if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(

View file

@ -6,12 +6,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/oidc_session_json_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/oidc_session_json_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/sign_in_flows/calc_redirect_url.dart';
Future<void> oidcLoginFlow( Future<void> oidcLoginFlow(
Client client, Client client,
@ -19,24 +18,16 @@ Future<void> oidcLoginFlow(
bool signUp, bool signUp,
) async { ) async {
Logs().i('Starting Matrix Native OIDC Flow...'); Logs().i('Starting Matrix Native OIDC Flow...');
final redirectUrl = kIsWeb
? Uri.parse(html.window.location.href.split('#').first.split('?').first)
: (PlatformInfos.isMobile || PlatformInfos.isMacOS)
? Uri.parse('${AppConfig.appOpenUrlScheme.toLowerCase()}:/login')
: Uri.parse('http://localhost:3001/login');
final urlScheme = final (redirectUrl, urlScheme) = calcRedirectUrl();
(PlatformInfos.isMobile || PlatformInfos.isWeb || PlatformInfos.isMacOS)
? redirectUrl.scheme
: 'http://localhost:3001';
final clientUri = Uri.parse(AppConfig.website); final clientUri = Uri.parse(AppSettings.website.value);
final supportWebPlatform = final supportWebPlatform =
kIsWeb && kIsWeb &&
kReleaseMode && kReleaseMode &&
redirectUrl.scheme == 'https' && redirectUrl.scheme == 'https' &&
redirectUrl.host.contains(clientUri.host); redirectUrl.host.contains(clientUri.host);
if (!supportWebPlatform) { if (kIsWeb && !supportWebPlatform) {
Logs().w( Logs().w(
'OIDC Application Type web is not supported. Using native now. Please use this instance not in production!', 'OIDC Application Type web is not supported. Using native now. Please use this instance not in production!',
); );
@ -50,9 +41,9 @@ Future<void> oidcLoginFlow(
clientInformation: OidcClientInformation( clientInformation: OidcClientInformation(
clientName: AppSettings.applicationName.value, clientName: AppSettings.applicationName.value,
clientUri: clientUri, clientUri: clientUri,
logoUri: Uri.parse('https://fluffy.chat/assets/favicon.png'), logoUri: Uri.parse(AppSettings.logoUrl.value),
tosUri: null, tosUri: Uri.parse(AppSettings.tos.value),
policyUri: AppConfig.privacyUrl, policyUri: Uri.parse(AppSettings.privacyPolicy.value),
), ),
); );

View file

@ -1,40 +1,28 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/sign_in_flows/calc_redirect_url.dart';
Future<void> ssoLoginFlow( Future<void> ssoLoginFlow(
Client client, Client client,
BuildContext context, BuildContext context,
bool signUp, bool signUp,
) async { ) async {
final redirectUrl = kIsWeb final (redirectUrl, urlScheme) = calcRedirectUrl(withAuthHtmlPath: true);
? Uri.parse(
html.window.location.href,
).resolveUri(Uri(pathSegments: ['auth.html'])).toString()
: (PlatformInfos.isMobile || PlatformInfos.isMacOS)
? '${AppConfig.appOpenUrlScheme.toLowerCase()}://login'
: 'http://localhost:3001//login';
Logs().i('Starting legacy SSO Flow with redirect URL', redirectUrl); Logs().i('Starting legacy SSO Flow with redirect URL', redirectUrl);
final url = client.homeserver!.replace( final url = client.homeserver!.replace(
path: '/_matrix/client/v3/login/sso/redirect', path: '/_matrix/client/v3/login/sso/redirect',
queryParameters: { queryParameters: {
'redirectUrl': redirectUrl, 'redirectUrl': redirectUrl.toString(),
'action': signUp ? 'register' : 'login', 'action': signUp ? 'register' : 'login',
}, },
); );
final urlScheme =
(PlatformInfos.isMobile || PlatformInfos.isWeb || PlatformInfos.isMacOS)
? Uri.parse(redirectUrl).scheme
: 'http://localhost:3001';
final result = await FlutterWebAuth2.authenticate( final result = await FlutterWebAuth2.authenticate(
url: url.toString(), url: url.toString(),
callbackUrlScheme: urlScheme, callbackUrlScheme: urlScheme,

View file

@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:particles_network/particles_network.dart'; import 'package:particles_network/particles_network.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
@ -118,7 +118,7 @@ class _PrivacyButtons extends StatelessWidget {
mainAxisAlignment: mainAxisAlignment, mainAxisAlignment: mainAxisAlignment,
children: [ children: [
TextButton( TextButton(
onPressed: () => launchUrlString(AppConfig.website), onPressed: () => launchUrlString(AppSettings.website.value),
child: Text(L10n.of(context).website, style: shadowTextStyle), child: Text(L10n.of(context).website, style: shadowTextStyle),
), ),
TextButton( TextButton(
@ -126,7 +126,7 @@ class _PrivacyButtons extends StatelessWidget {
child: Text(L10n.of(context).help, style: shadowTextStyle), child: Text(L10n.of(context).help, style: shadowTextStyle),
), ),
TextButton( TextButton(
onPressed: () => launchUrl(AppConfig.privacyUrl), onPressed: () => launchUrlString(AppSettings.privacyPolicy.value),
child: Text(L10n.of(context).privacy, style: shadowTextStyle), child: Text(L10n.of(context).privacy, style: shadowTextStyle),
), ),
TextButton( TextButton(