Skip to content
This repository has been archived by the owner on Sep 14, 2024. It is now read-only.

WIP: Feature/loginflow #193

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.nextcloud_cookbook_flutter"
minSdkVersion 18
minSdkVersion 20
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
Binary file added assets/icon.svg.vec
Binary file not shown.
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class _AppState extends State<App> {
return const LoginScreen();
case AuthenticationStatus.invalid:
return const LoginScreen(
invalidCredentials: true,
//invalidCredentials: true,
);
case AuthenticationStatus.error:
return LoadingErrorScreen(message: state.error!);
Expand Down
64 changes: 50 additions & 14 deletions lib/src/blocs/authentication/authentication_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:nc_cookbook_api/nc_cookbook_api.dart';
import 'package:nextcloud_cookbook_flutter/src/models/app_authentication.dart';
import 'package:nextcloud_cookbook_flutter/src/services/services.dart';

part 'authentication_event.dart';
part 'authentication_exception.dart';
part 'authentication_state.dart';

class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
AuthenticationBloc() : super(AuthenticationState()) {
AuthenticationBloc() : super(const AuthenticationState()) {
on<AppStarted>(_mapAppStartedEventToState);
on<LoggedIn>(_mapLoggedInEventToState);
on<LoggedOut>(_mapLoggedOutEventToState);
Expand All @@ -19,19 +21,28 @@ class AuthenticationBloc
AppStarted event,
Emitter<AuthenticationState> emit,
) async {
final hasToken = await userRepository.hasAppAuthentication();

if (hasToken) {
await userRepository.loadAppAuthentication();
if (userRepository.hasAuthentidation) {
try {
await userRepository.loadAppAuthentication();

final validCredentials = await userRepository.checkAppAuthentication();

if (validCredentials) {
emit(AuthenticationState(status: AuthenticationStatus.authenticated));
final apiVersion = await UserRepository().fetchApiVersion();
emit(
AuthenticationState(
status: AuthenticationStatus.authenticated,
apiVersion: apiVersion,
),
);
} else {
await userRepository.deleteAppAuthentication();
emit(AuthenticationState(status: AuthenticationStatus.invalid));
emit(const AuthenticationState(status: AuthenticationStatus.invalid));
}
} on LoadAuthException {
emit(
const AuthenticationState(status: AuthenticationStatus.authenticated),
);
} catch (e) {
emit(
AuthenticationState(
Expand All @@ -41,25 +52,50 @@ class AuthenticationBloc
);
}
} else {
emit(AuthenticationState(status: AuthenticationStatus.unauthenticated));
emit(
const AuthenticationState(
status: AuthenticationStatus.unauthenticated,
),
);
}
}

Future<void> _mapLoggedInEventToState(
LoggedIn event,
Emitter<AuthenticationState> emit,
) async {
emit(AuthenticationState());
await userRepository.persistAppAuthentication(event.appAuthentication);
emit(AuthenticationState(status: AuthenticationStatus.authenticated));
emit(const AuthenticationState());
try {
await userRepository.persistAppAuthentication(event.appAuthentication);

final apiVersion = await UserRepository().fetchApiVersion();
emit(
AuthenticationState(
status: AuthenticationStatus.authenticated,
apiVersion: apiVersion,
),
);
} catch (e) {
emit(
AuthenticationState(
status: AuthenticationStatus.error,
error: e.toString(),
),
);
}
}

Future<void> _mapLoggedOutEventToState(
LoggedOut event,
Emitter<AuthenticationState> emit,
) async {
emit(AuthenticationState());
await userRepository.deleteAppAuthentication();
emit(AuthenticationState(status: AuthenticationStatus.unauthenticated));
emit(const AuthenticationState());
try {
await userRepository.deleteAppAuthentication();
} finally {
emit(
const AuthenticationState(status: AuthenticationStatus.unauthenticated),
);
}
}
}
5 changes: 5 additions & 0 deletions lib/src/blocs/authentication/authentication_exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
part of 'authentication_bloc.dart';

abstract class AuthException implements Exception {}

class LoadAuthException extends AuthException {}
12 changes: 8 additions & 4 deletions lib/src/blocs/authentication/authentication_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ class AuthenticationState extends Equatable {
const AuthenticationState({
this.status = AuthenticationStatus.loading,
this.error,
}) : assert(
(status != AuthenticationStatus.error && error == null) ||
(status == AuthenticationStatus.error && error != null),
this.apiVersion,
}) : assert(error == null || status == AuthenticationStatus.error),
assert(
apiVersion == null || status == AuthenticationStatus.authenticated,
);
final AuthenticationStatus status;
final String? error;

/// The [APIVersion] authenticated against
final APIVersion? apiVersion;

@override
List<Object?> get props => [status, error];
List<Object?> get props => [status, error, apiVersion];
}
85 changes: 55 additions & 30 deletions lib/src/blocs/login/login_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import 'dart:async';

import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud_cookbook_flutter/src/blocs/authentication/authentication_bloc.dart';
import 'package:nextcloud_cookbook_flutter/src/models/app_authentication.dart';
import 'package:nextcloud_cookbook_flutter/src/services/services.dart';
import 'package:nextcloud_cookbook_flutter/src/util/nextcloud_login_qr_util.dart';
import 'package:nextcloud_cookbook_flutter/src/util/url_validator.dart';

part 'login_event.dart';
Expand All @@ -11,47 +16,67 @@ part 'login_state.dart';
class LoginBloc extends Bloc<LoginEvent, LoginState> {
LoginBloc({
required this.authenticationBloc,
}) : super(LoginState()) {
on<LoginButtonPressed>(_mapLoginButtonPressedEventToState);
}) : super(const LoginState()) {
on<LoginFlowStart>(_mapLoginFlowStartEventToState);
on<LoginQRScenned>(_mapLoginQRScannedEventToState);
}
final UserRepository userRepository = UserRepository();
final AuthenticationBloc authenticationBloc;

Future<void> _mapLoginButtonPressedEventToState(
LoginButtonPressed event,
Future<void> _mapLoginFlowStartEventToState(
LoginFlowStart event,
Emitter<LoginState> emit,
) async {
emit(LoginState(status: LoginStatus.loading));

assert(URLUtils.isSanitized(event.serverURL));
try {
AppAuthentication appAuthentication;
assert(URLUtils.isSanitized(event.serverURL));
final client = NextcloudClient(event.serverURL);
final init = await client.core.initLoginFlow();
emit(LoginState(status: LoginStatus.loading, url: init.login));
Timer.periodic(const Duration(seconds: 2), (timer) async {
try {
final result =
await client.core.getLoginFlowResult(token: init.poll.token);
timer.cancel();

authenticationBloc.add(
LoggedIn(
appAuthentication: AppAuthentication(
server: result.server,
loginName: result.loginName,
appPassword: result.appPassword,
isSelfSignedCertificate: false,
),
),
);
} catch (e) {
debugPrint(e.toString());
}
});
} catch (e) {
emit(LoginState(status: LoginStatus.failure, error: e.toString()));
}
}

if (!event.isAppPassword) {
appAuthentication = await userRepository.authenticate(
event.serverURL,
event.username,
event.originalBasicAuth,
isSelfSignedCertificate: event.isSelfSignedCertificate,
);
} else {
appAuthentication = await userRepository.authenticateAppPassword(
event.serverURL,
event.username,
event.originalBasicAuth,
isSelfSignedCertificate: event.isSelfSignedCertificate,
);
}
Future<void> _mapLoginQRScannedEventToState(
LoginQRScenned event,
Emitter<LoginState> emit,
) async {
assert(event.uri.isScheme('nc'));
try {
final auth = parseNCLoginQR(event.uri);

authenticationBloc.add(LoggedIn(appAuthentication: appAuthentication));
emit(LoginState());
} catch (error) {
emit(
LoginState(
status: LoginStatus.failure,
error: error.toString(),
authenticationBloc.add(
LoggedIn(
appAuthentication: AppAuthentication(
server: auth['server']!,
loginName: auth['user']!,
appPassword: auth['password']!,
isSelfSignedCertificate: false,
),
),
);
} catch (e) {
emit(LoginState(status: LoginStatus.failure, error: e.toString()));
}
}
}
25 changes: 7 additions & 18 deletions lib/src/blocs/login/login_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,14 @@ abstract class LoginEvent extends Equatable {
List<Object> get props => [];
}

class LoginButtonPressed extends LoginEvent {
const LoginButtonPressed({
required this.serverURL,
required this.username,
required this.originalBasicAuth,
required this.isAppPassword,
required this.isSelfSignedCertificate,
});
class LoginFlowStart extends LoginEvent {
const LoginFlowStart(this.serverURL);

final String serverURL;
final String username;
final String originalBasicAuth;
final bool isAppPassword;
final bool isSelfSignedCertificate;
}

@override
List<Object> get props =>
[serverURL, username, isAppPassword, isSelfSignedCertificate];
class LoginQRScenned extends LoginEvent {
const LoginQRScenned(this.uri);

@override
String toString() =>
'LoginButtonPressed {serverURL: $serverURL, username: $username, isAppPassword: $isAppPassword}, isSelfSignedCertificate: $isSelfSignedCertificate';
final Uri uri;
}
7 changes: 3 additions & 4 deletions lib/src/blocs/login/login_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ class LoginState extends Equatable {
const LoginState({
this.status = LoginStatus.initial,
this.error,
}) : assert(
(status != LoginStatus.failure && error == null) ||
(status == LoginStatus.failure && error != null),
);
this.url,
}) : assert(error == null || status == LoginStatus.failure);
final LoginStatus status;
final String? error;
final String? url;

@override
List<Object?> get props => [status, error];
Expand Down
Loading