Browse Source

neon,app: Handle qrcode login from other apps

pull/291/head
jld3103 2 years ago committed by Nikolas Rimikis
parent
commit
51ead3b85d
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 7
      packages/app/android/app/src/main/AndroidManifest.xml
  2. 70
      packages/neon/neon/lib/src/pages/login_qrcode.dart
  3. 34
      packages/neon/neon/lib/src/router.dart
  4. 23
      packages/neon/neon/lib/src/router.g.dart
  5. 47
      packages/neon/neon/lib/src/utils/login_qrcode.dart

7
packages/app/android/app/src/main/AndroidManifest.xml

@ -28,6 +28,13 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nc" android:host="login" />
</intent-filter>
</activity>
<receiver android:enabled="true" android:name=".EmbeddedDistributor" android:exported="false">
<intent-filter>

70
packages/neon/neon/lib/src/pages/login_qrcode.dart

@ -1,7 +1,10 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_zxing/flutter_zxing.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/utils/exceptions.dart';
import 'package:neon/src/utils/login_qrcode.dart';
import 'package:neon/src/widgets/exception.dart';
class LoginQrcodePage extends StatefulWidget {
@ -14,7 +17,6 @@ class LoginQrcodePage extends StatefulWidget {
}
class _LoginQrcodePageState extends State<LoginQrcodePage> {
final _urlRegex = RegExp(r'^nc://login/user:(.*)&password:(.*)&server:(.*)$');
String? _lastErrorURL;
@override
@ -35,22 +37,11 @@ class _LoginQrcodePageState extends State<LoginQrcodePage> {
if (url == null) {
throw InvalidQrcodeException();
}
final match = _urlRegex.allMatches(url).single;
if (match.groupCount != 3) {
final match = LoginQrcode.tryParse(url);
if (match == null) {
throw InvalidQrcodeException();
}
final loginName = match.group(1)!;
final password = match.group(2)!;
final serverURL = match.group(3)!;
final result = await LoginCheckServerStatusRoute(serverURL: serverURL).push<bool>(context);
if ((result ?? false) && mounted) {
LoginCheckAccountRoute(
serverURL: serverURL,
loginName: loginName,
password: password,
).pushReplacement(context);
}
await processLoginQrcode(context, match);
} catch (e, s) {
if (_lastErrorURL != url) {
debugPrint(e.toString());
@ -64,3 +55,52 @@ class _LoginQrcodePageState extends State<LoginQrcodePage> {
),
);
}
class LoginQrcodeIntermediatePage extends StatefulWidget {
const LoginQrcodeIntermediatePage({
required this.serverURL,
required this.loginName,
required this.password,
super.key,
});
final String serverURL;
final String loginName;
final String password;
@override
State<LoginQrcodeIntermediatePage> createState() => _LoginQrcodeIntermediatePageState();
}
class _LoginQrcodeIntermediatePageState extends State<LoginQrcodeIntermediatePage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((final _) {
unawaited(
processLoginQrcode(
context,
LoginQrcode(
server: widget.serverURL,
user: widget.loginName,
password: widget.password,
),
),
);
});
}
@override
Widget build(final BuildContext context) => const SizedBox();
}
Future processLoginQrcode(final BuildContext context, final LoginQrcode qrcode) async {
final result = await LoginCheckServerStatusRoute(serverURL: qrcode.server).push<bool>(context);
if ((result ?? false) && context.mounted) {
LoginCheckAccountRoute(
serverURL: qrcode.server,
loginName: qrcode.user,
password: qrcode.password,
).pushReplacement(context);
}
}

34
packages/neon/neon/lib/src/router.dart

@ -13,6 +13,7 @@ import 'package:neon/src/pages/login_flow.dart';
import 'package:neon/src/pages/login_qrcode.dart';
import 'package:neon/src/pages/nextcloud_app_settings.dart';
import 'package:neon/src/pages/settings.dart';
import 'package:neon/src/utils/login_qrcode.dart';
import 'package:neon/src/utils/stream_listenable.dart';
import 'package:provider/provider.dart';
@ -30,6 +31,15 @@ class AppRouter extends GoRouter {
redirect: (final context, final state) {
final account = accountsBloc.activeAccount.valueOrNull;
final loginQrcode = LoginQrcode.tryParse(state.location);
if (loginQrcode != null) {
return LoginQrcodeIntermediateRoute(
serverURL: loginQrcode.server,
loginName: loginQrcode.user,
password: loginQrcode.password,
).location;
}
// redirect to loginscreen when no account is logged in
if (account == null && !state.location.startsWith(const LoginRoute().location)) {
return const LoginRoute().location;
@ -110,6 +120,10 @@ class HomeRoute extends GoRouteData {
path: 'qrcode',
name: 'loginQrcode',
),
TypedGoRoute<LoginQrcodeIntermediateRoute>(
path: 'qrcode/intermediate/:serverURL/:loginName/:password',
name: 'loginQrcodeIntermediate',
),
TypedGoRoute<LoginCheckServerStatusRoute>(
path: 'check/server/:serverURL',
name: 'checkServerStatus',
@ -150,6 +164,26 @@ class LoginQrcodeRoute extends GoRouteData {
Widget build(final BuildContext context, final GoRouterState state) => const LoginQrcodePage();
}
@immutable
class LoginQrcodeIntermediateRoute extends GoRouteData {
const LoginQrcodeIntermediateRoute({
required this.serverURL,
required this.loginName,
required this.password,
});
final String serverURL;
final String loginName;
final String password;
@override
Widget build(final BuildContext context, final GoRouterState state) => LoginQrcodeIntermediatePage(
serverURL: serverURL,
loginName: loginName,
password: password,
);
}
@immutable
class LoginCheckServerStatusRoute extends GoRouteData {
const LoginCheckServerStatusRoute({

23
packages/neon/neon/lib/src/router.g.dart

@ -130,6 +130,11 @@ RouteBase get $loginRoute => GoRouteData.$route(
name: 'loginQrcode',
factory: $LoginQrcodeRouteExtension._fromState,
),
GoRouteData.$route(
path: 'qrcode/intermediate/:serverURL/:loginName/:password',
name: 'loginQrcodeIntermediate',
factory: $LoginQrcodeIntermediateRouteExtension._fromState,
),
GoRouteData.$route(
path: 'check/server/:serverURL',
name: 'checkServerStatus',
@ -192,6 +197,24 @@ extension $LoginQrcodeRouteExtension on LoginQrcodeRoute {
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}
extension $LoginQrcodeIntermediateRouteExtension on LoginQrcodeIntermediateRoute {
static LoginQrcodeIntermediateRoute _fromState(GoRouterState state) => LoginQrcodeIntermediateRoute(
serverURL: state.pathParameters['serverURL']!,
loginName: state.pathParameters['loginName']!,
password: state.pathParameters['password']!,
);
String get location => GoRouteData.$location(
'/login/qrcode/intermediate/${Uri.encodeComponent(serverURL)}/${Uri.encodeComponent(loginName)}/${Uri.encodeComponent(password)}',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}
extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
static LoginCheckServerStatusRoute _fromState(GoRouterState state) => LoginCheckServerStatusRoute(
serverURL: state.pathParameters['serverURL']!,

47
packages/neon/neon/lib/src/utils/login_qrcode.dart

@ -0,0 +1,47 @@
import 'package:meta/meta.dart';
@internal
class LoginQrcode {
LoginQrcode({
required this.server,
required this.user,
required this.password,
});
static final _loginQrcodeUrlRegex = RegExp(r'^nc://login/user:(.*)&password:(.*)&server:(.*)$');
static final _loginQrcodePathRegex = RegExp(r'^/user:(.*)&password:(.*)&server:(.*)$');
static LoginQrcode parse(final String url) {
for (final regex in [_loginQrcodeUrlRegex, _loginQrcodePathRegex]) {
final matches = regex.allMatches(url);
if (matches.isEmpty) {
continue;
}
final match = matches.single;
if (match.groupCount != 3) {
continue;
}
return LoginQrcode(
server: match.group(3)!,
user: match.group(1)!,
password: match.group(2)!,
);
}
throw const FormatException();
}
static LoginQrcode? tryParse(final String url) {
try {
return parse(url);
} on FormatException {
return null;
}
}
final String server;
final String user;
final String password;
}
Loading…
Cancel
Save