Skip to content

TF-3455 Add applicative token login method #3527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/token_oidc.dart';

class SigningInWithApplicativeToken extends LoadingState {}

class SignInWithApplicativeTokenSuccess extends UIState {
final TokenOIDC tokenOIDC;
final Uri baseUri;
final OIDCConfiguration oidcConfiguration;

SignInWithApplicativeTokenSuccess(this.tokenOIDC, this.baseUri, this.oidcConfiguration);

@override
List<Object> get props => [tokenOIDC, baseUri, oidcConfiguration];
}

class SignInWithApplicativeTokenFailure extends FeatureFailure {
SignInWithApplicativeTokenFailure({super.exception});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart';
import 'package:model/account/authentication_type.dart';
import 'package:model/account/personal_account.dart';
import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/token_id.dart';
import 'package:model/oidc/token_oidc.dart';
import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart';
import 'package:tmail_ui_user/features/login/domain/state/sign_in_with_applicative_token_state.dart';
import 'package:uuid/uuid.dart';

class SignInWithApplicativeTokenInteractor {
final CredentialRepository _credentialRepository;
final AuthenticationOIDCRepository _authenticationOIDCRepository;
final AccountRepository _accountRepository;

const SignInWithApplicativeTokenInteractor(
this._credentialRepository,
this._authenticationOIDCRepository,
this._accountRepository,
);

Stream<Either<Failure, Success>> execute({
required String applicativeToken,
required Uri baseUri,
required Uuid uuid,
}) async* {
try {
yield Right(SigningInWithApplicativeToken());
final tokenOIDC = TokenOIDC(applicativeToken, TokenId(uuid.v4()), '');
final oidcConfiguration = OIDCConfiguration(
clientId: '',
scopes: [],
authority: '',
);
await Future.wait([
_credentialRepository.saveBaseUrl(baseUri),
_authenticationOIDCRepository.persistTokenOIDC(tokenOIDC),
_authenticationOIDCRepository.persistOidcConfiguration(oidcConfiguration),
]);

await _accountRepository.setCurrentAccount(
PersonalAccount(
tokenOIDC.tokenIdHash,
AuthenticationType.oidc,
isSelected: true
)
);

yield Right(SignInWithApplicativeTokenSuccess(
tokenOIDC,
baseUri,
oidcConfiguration,
));
} catch (e) {
logError('SignInWithApplicativeTokenInteractor::execute: $e');
yield Left(SignInWithApplicativeTokenFailure(exception: e));
}
}
}
7 changes: 7 additions & 0 deletions lib/features/login/presentation/login_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_con
import 'package:tmail_ui_user/features/login/domain/usecases/get_token_oidc_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/sign_in_with_applicative_token_interactor.dart';
import 'package:tmail_ui_user/features/login/presentation/login_controller.dart';
import 'package:tmail_ui_user/features/starting_page/data/datasource/saas_authentication_datasource.dart';
import 'package:tmail_ui_user/features/starting_page/data/datasource_impl/saas_authentication_datasource_impl.dart';
Expand Down Expand Up @@ -54,6 +55,7 @@ class LoginBindings extends BaseBindings {
Get.find<GetAllRecentLoginUsernameOnMobileInteractor>(),
Get.find<DNSLookupToGetJmapUrlInteractor>(),
Get.find<SignInTwakeWorkplaceInteractor>(),
Get.find<SignInWithApplicativeTokenInteractor>(),
));
}

Expand Down Expand Up @@ -117,6 +119,11 @@ class LoginBindings extends BaseBindings {
Get.find<AccountRepository>(),
Get.find<CredentialRepository>(),
));
Get.lazyPut(() => SignInWithApplicativeTokenInteractor(
Get.find<CredentialRepository>(),
Get.find<AuthenticationOIDCRepository>(),
Get.find<AccountRepository>(),
));
}

@override
Expand Down
63 changes: 52 additions & 11 deletions lib/features/login/presentation/login_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import 'package:tmail_ui_user/features/login/domain/state/get_oidc_configuration
import 'package:tmail_ui_user/features/login/domain/state/get_oidc_is_available_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_stored_oidc_configuration_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_token_oidc_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/sign_in_with_applicative_token_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/update_authentication_account_state.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/authenticate_oidc_on_browser_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/check_oidc_is_available_interactor.dart';
Expand All @@ -52,6 +54,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_con
import 'package:tmail_ui_user/features/login/domain/usecases/get_token_oidc_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/sign_in_with_applicative_token_interactor.dart';
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart';
import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_workplace_state.dart';
Expand Down Expand Up @@ -82,6 +85,7 @@ class LoginController extends ReloadableController {
final GetAllRecentLoginUsernameOnMobileInteractor _getAllRecentLoginUsernameOnMobileInteractor;
final DNSLookupToGetJmapUrlInteractor _dnsLookupToGetJmapUrlInteractor;
final SignInTwakeWorkplaceInteractor _signInTwakeWorkplaceInteractor;
final SignInWithApplicativeTokenInteractor _signInWithApplicativeTokenInteractor;

final TextEditingController urlInputController = TextEditingController();
final TextEditingController usernameInputController = TextEditingController();
Expand All @@ -96,6 +100,20 @@ class LoginController extends ReloadableController {
UserName? _username;
Password? _password;
Uri? _baseUri;
String _applicativeToken = '';
bool get isShowingMessage {
return viewState.value.fold(
(failure) {
// Ignore message when login by applicative token
if (failure is UpdateAccountCacheFailure && _password == null && _applicativeToken.isNotEmpty) {
return false;
}

return true;
},
(success) => true
);
}

DeepLinksManager? _deepLinksManager;
StreamSubscription<DeepLinkData?>? _deepLinkDataStreamSubscription;
Expand All @@ -115,6 +133,7 @@ class LoginController extends ReloadableController {
this._getAllRecentLoginUsernameOnMobileInteractor,
this._dnsLookupToGetJmapUrlInteractor,
this._signInTwakeWorkplaceInteractor,
this._signInWithApplicativeTokenInteractor,
);

@override
Expand Down Expand Up @@ -200,6 +219,12 @@ class LoginController extends ReloadableController {
tokenOIDC: success.tokenOIDC,
oidcConfiguration: success.oidcConfiguration,
);
} else if (success is SignInWithApplicativeTokenSuccess) {
_synchronizeTokenAndGetSession(
baseUri: success.baseUri,
tokenOIDC: success.tokenOIDC,
oidcConfiguration: success.oidcConfiguration,
);
} else {
super.handleSuccessViewState(success);
}
Expand Down Expand Up @@ -366,25 +391,29 @@ class LoginController extends ReloadableController {
log('LoginController::handleLoginPressed:_currentBaseUrl: $_currentBaseUrl | _username: $_username | _password: $_password');
if (_currentBaseUrl == null) {
consumeState(Stream.value(Left(AuthenticationUserFailure(CanNotFoundBaseUrl()))));
} else if (_username == null) {
} else if (_username == null && _applicativeToken.isEmpty) {
consumeState(Stream.value(Left(AuthenticationUserFailure(CanNotFoundUserName()))));
} else if (_password == null) {
} else if (_password == null && _applicativeToken.isEmpty) {
consumeState(Stream.value(Left(AuthenticationUserFailure(CanNotFoundPassword()))));
} else {
if (PlatformInfo.isMobile && loginFormType.value == LoginFormType.credentialForm) {
if (PlatformInfo.isMobile && loginFormType.value == LoginFormType.credentialForm && _username != null) {
TextInput.finishAutofillContext();
if (_username!.value.isEmail) {
_storeUsernameToCache(_username!.value);
}
}

consumeState(
_authenticationInteractor.execute(
baseUrl: _currentBaseUrl!,
userName: _username!,
password: _password!
)
);
if (_password != null && _username != null) {
consumeState(
_authenticationInteractor.execute(
baseUrl: _currentBaseUrl!,
userName: _username!,
password: _password!
)
);
} else {
_loginByApplicativeToken(_applicativeToken);
}
}
}

Expand Down Expand Up @@ -621,6 +650,18 @@ class LoginController extends ReloadableController {
loginFormType.value == LoginFormType.passwordForm ||
loginFormType.value == LoginFormType.credentialForm;

void onApplicativeTokenChange(String value) {
_applicativeToken = value;
}

void _loginByApplicativeToken(String token) {
consumeState(_signInWithApplicativeTokenInteractor.execute(
applicativeToken: token,
baseUri: _currentBaseUrl!,
uuid: uuid,
));
}

@override
void onClose() {
passFocusNode.dispose();
Expand All @@ -634,4 +675,4 @@ class LoginController extends ReloadableController {
}
super.onClose();
}
}
}
55 changes: 46 additions & 9 deletions lib/features/login/presentation/login_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/widget/application_version_widget.dart';
import 'package:tmail_ui_user/features/base/widget/recent_item_tile_widget.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart';
import 'package:tmail_ui_user/features/login/presentation/base_login_view.dart';
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/features/login/presentation/privacy_link_widget.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/applicative_token_field.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/dns_lookup_input_form.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/horizontal_progress_loading_button.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/login_back_button.dart';
Expand Down Expand Up @@ -90,10 +92,22 @@ class LoginView extends BaseLoginView {
style: const TextStyle(fontSize: 32, color: AppColor.colorNameEmail, fontWeight: FontWeight.w900)
)
),
Obx(() => LoginMessageWidget(
formType: controller.loginFormType.value,
viewState: controller.viewState.value,
)),
KeyboardVisibilityBuilder(
builder: (context, visible) {
return Obx(() {
if (visible && (controller.loginFormType.value == LoginFormType.passwordForm
|| controller.loginFormType.value == LoginFormType.credentialForm)) {
return const SizedBox.shrink();
}

return LoginMessageWidget(
formType: controller.loginFormType.value,
viewState: controller.viewState.value,
isShowingMessage: controller.isShowingMessage,
);
});
}
),
Obx(() {
switch (controller.loginFormType.value) {
case LoginFormType.dnsLookupForm:
Expand Down Expand Up @@ -122,12 +136,35 @@ class LoginView extends BaseLoginView {
return const SizedBox.shrink();
}
}),
Obx(() {
switch (controller.loginFormType.value) {
case LoginFormType.passwordForm:
case LoginFormType.credentialForm:
return ApplicativeTokenField(
onChanged: controller.onApplicativeTokenChange,
appLocalizations: AppLocalizations.of(context),
imagePath: controller.imagePaths,
);
default:
return const SizedBox.shrink();
}
}),
_buildLoadingProgress(context),
const Padding(
padding: EdgeInsets.only(top: 16),
child: PrivacyLinkWidget(),
KeyboardVisibilityBuilder(
builder: (context, visible) {
if (visible) return const SizedBox.shrink();

return const Column(
children: [
Padding(
padding: EdgeInsets.only(top: 16),
child: PrivacyLinkWidget(),
),
ApplicationVersionWidget(),
],
);
},
),
const ApplicationVersionWidget(),
]
),
)
Expand Down Expand Up @@ -251,4 +288,4 @@ class LoginView extends BaseLoginView {
}
));
}
}
}
Loading
Loading