Skip to content

Commit

Permalink
authpass_cloud: wait 30 seconds before showing resend confirm email, …
Browse files Browse the repository at this point in the history
…make it less prominent #259
  • Loading branch information
hpoul committed Aug 27, 2021
1 parent 12fd3a7 commit f16b3a8
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 53 deletions.
18 changes: 15 additions & 3 deletions authpass/lib/bloc/authpass_cloud_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ class _StoredToken {
_StoredToken({
required this.authToken,
required this.isConfirmed,
});
required DateTime? createdAt,
}) : createdAt = createdAt ?? clock.now().toUtc();
factory _StoredToken.fromJson(Map<String, dynamic> json) =>
_$StoredTokenFromJson(json);
Map<String, dynamic> toJson() => _$StoredTokenToJson(this);

final String authToken;
final bool isConfirmed;
final DateTime createdAt;
}

enum TokenStatus {
Expand Down Expand Up @@ -129,6 +131,8 @@ class AuthPassCloudBloc with ChangeNotifier {
? TokenStatus.confirmed
: TokenStatus.created;

DateTime? get tokenCreatedAt => _storedToken?.createdAt;

Future<BiometricStorageFile> _getStorageFile() async {
return await BiometricStorage().getStorage(
nonNls('${env.storageNamespace ?? ''}AuthPassCloud'),
Expand Down Expand Up @@ -229,16 +233,24 @@ class AuthPassCloudBloc with ChangeNotifier {
await _saveToken(_StoredToken(
authToken: response.authToken,
isConfirmed: false,
createdAt: null,
));
}

Future<bool> checkConfirmed() async {
ArgumentError.checkNotNull(_storedToken);
final _storedToken = this._storedToken;
if (_storedToken == null) {
throw ArgumentError('_storedToken');
}
final client = await _getClient();
final response = await client.emailStatusGet().requireSuccess();
if (response.status == EmailStatusGetResponseBody200Status.confirmed) {
await _saveToken(
_StoredToken(authToken: _storedToken!.authToken, isConfirmed: true),
_StoredToken(
authToken: _storedToken.authToken,
isConfirmed: true,
createdAt: _storedToken.createdAt,
),
);
return true;
}
Expand Down
4 changes: 4 additions & 0 deletions authpass/lib/bloc/authpass_cloud_bloc.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions authpass/lib/env/_base.freezed.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target

Expand Down
8 changes: 8 additions & 0 deletions authpass/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,14 @@
"@changeMasterPasswordScreenTitle": {
"description": "app bar title for change password screen."
},
"authPassCloudAuthClickedLink": "I received email and visited link",
"@authPassCloudAuthClickedLink": {
"description": ""
},
"authPassCloudAuthNotConfirmed": "Email address was not yet confirmed. Make sure to click the link in the email you received and solve the captcha to confirm your email address.",
"@authPassCloudAuthNotConfirmed": {
"description": ""
},
"unexpectedError": "Unexpected Error: {error}",
"@unexpectedError": {
"placeholders": {
Expand Down
102 changes: 71 additions & 31 deletions authpass/lib/ui/screens/cloud/cloud_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:authpass/bloc/authpass_cloud_bloc.dart';
import 'package:authpass/ui/widgets/link_button.dart';
import 'package:authpass/utils/constants.dart';
import 'package:authpass/utils/dialog_utils.dart';
import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_async_utils/flutter_async_utils.dart';
Expand Down Expand Up @@ -146,14 +147,17 @@ class _ConfirmEmailAddress extends StatefulWidget {
__ConfirmEmailAddressState createState() => __ConfirmEmailAddressState();
}

class __ConfirmEmailAddressState extends State<_ConfirmEmailAddress> {
class __ConfirmEmailAddressState extends State<_ConfirmEmailAddress>
with FutureTaskStateMixin {
Timer? _timer;
bool _showResendButton = false;
late Analytics _analytics;
late DateTime _tokenCreatedAt;

@override
void didChangeDependencies() {
super.didChangeDependencies();
_tokenCreatedAt = widget.bloc.tokenCreatedAt ?? clock.now();
if (_timer == null || !_timer!.isActive) {
_scheduleCheck();
}
Expand All @@ -166,55 +170,91 @@ class __ConfirmEmailAddressState extends State<_ConfirmEmailAddress> {
_logger.fine('No longer mounted. Skipping checkConfirmed.');
return;
}
if (await widget.bloc.checkConfirmed()) {
_analytics.events.trackCloudAuth(CloudAuthAction.authSuccess);
}
_checkConfirmed();
unawaited(_scheduleCheck());
if (!_showResendButton) {
_logger.fine('${clock.now().difference(_tokenCreatedAt)}');
if (!_showResendButton &&
clock.now().difference(_tokenCreatedAt) >
const Duration(seconds: 30)) {
setState(() {
_showResendButton = true;
});
}
});
}

Future<bool> _checkConfirmed() async {
if (await widget.bloc.checkConfirmed()) {
_analytics.events.trackCloudAuth(CloudAuthAction.authSuccess);
return true;
}
return false;
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final loc = AppLocalizations.of(context);
return Column(
children: <Widget>[
children: [
Text(loc.authPassCloudAuthConfirmEmail),
const SizedBox(height: 32),
Text(
loc.authPassCloudAuthConfirmEmailExplain,
style: theme.textTheme.caption,
),
...?_showResendButton
? [
const SizedBox(height: 32),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 320),
child: Text(
loc.authPassCloudAuthResendExplain,
textAlign: TextAlign.center,
style: theme.textTheme.caption!.copyWith(height: 1.4),
maxLines: 5,
),
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () {
context
.read<Analytics>()
.events
.trackCloudAuth(CloudAuthAction.authResend);
widget.bloc.clearToken();
},
child: Text(loc.authPassCloudAuthResendButtonLabel),
),
]
: null,
const SizedBox(height: 32),
const CircularProgressIndicator(),
const SizedBox(height: 32),
if (_showResendButton) ...[
TextButton(
onPressed: asyncTaskCallback((task) async {
_timer?.cancel();
try {
if (!await _checkConfirmed()) {
await DialogUtils.showSimpleAlertDialog(
context,
null,
loc.authPassCloudAuthNotConfirmed,
routeAppend: 'authPassCloudAuthNotConfirmed',
moreActions: [
TextButton(
onPressed: () {
DialogUtils.openUrl(UrlConstants.forumUrl);
},
child: Text('Get help in the forum'),
)
],
);
}
} finally {
await _scheduleCheck();
}
}),
child: Text(loc.authPassCloudAuthClickedLink),
),
const SizedBox(height: 32),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 320),
child: Text(
loc.authPassCloudAuthResendExplain,
textAlign: TextAlign.center,
style: theme.textTheme.caption!.copyWith(height: 1.4),
maxLines: 5,
),
),
const SizedBox(height: 32),
TextButton(
onPressed: () {
context
.read<Analytics>()
.events
.trackCloudAuth(CloudAuthAction.authResend);
widget.bloc.clearToken();
},
child: Text(loc.authPassCloudAuthResendButtonLabel),
),
],
],
);
}
Expand Down
1 change: 1 addition & 0 deletions authpass/lib/ui/screens/cloud/cloud_viewmodel.freezed.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target

Expand Down
49 changes: 49 additions & 0 deletions authpass/lib/ui/widgets/icon_selector.freezed.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target

Expand Down Expand Up @@ -41,6 +42,12 @@ mixin _$SelectedIcon {
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function(KdbxIcon icon)? predefined,
TResult Function(KdbxCustomIcon custom)? custom,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(KdbxIcon icon)? predefined,
TResult Function(KdbxCustomIcon custom)? custom,
Expand All @@ -54,6 +61,12 @@ mixin _$SelectedIcon {
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(_SelectedIconPredefined value)? predefined,
TResult Function(_SelectedIconCustom value)? custom,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_SelectedIconPredefined value)? predefined,
TResult Function(_SelectedIconCustom value)? custom,
Expand Down Expand Up @@ -160,6 +173,15 @@ class _$_SelectedIconPredefined
return predefined(icon);
}

@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function(KdbxIcon icon)? predefined,
TResult Function(KdbxCustomIcon custom)? custom,
}) {
return predefined?.call(icon);
}

@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
Expand All @@ -182,6 +204,15 @@ class _$_SelectedIconPredefined
return predefined(this);
}

@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(_SelectedIconPredefined value)? predefined,
TResult Function(_SelectedIconCustom value)? custom,
}) {
return predefined?.call(this);
}

@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
Expand Down Expand Up @@ -287,6 +318,15 @@ class _$_SelectedIconCustom
return custom(this.custom);
}

@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function(KdbxIcon icon)? predefined,
TResult Function(KdbxCustomIcon custom)? custom,
}) {
return custom?.call(this.custom);
}

@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
Expand All @@ -309,6 +349,15 @@ class _$_SelectedIconCustom
return custom(this);
}

@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(_SelectedIconPredefined value)? predefined,
TResult Function(_SelectedIconCustom value)? custom,
}) {
return custom?.call(this);
}

@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
Expand Down
22 changes: 3 additions & 19 deletions authpass/lib/ui/widgets/savefile/save_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,9 @@ class SaveFileAs extends StatefulWidget {
}

class _SaveFileAsState extends State<SaveFileAs> with FutureTaskStateMixin {
KdbxBloc? kdbxBloc;

@override
void initState() {
super.initState();
}

@override
void didChangeDependencies() {
_init();
super.didChangeDependencies();
}

void _init() {
kdbxBloc ??= context.read<KdbxBloc>();
}

@override
Widget build(BuildContext context) {
final kdbxBloc = context.watch<KdbxBloc>();
return ListTile(
leading: widget.icon ?? Icon(widget.cs!.displayIcon.iconData),
title: Text(widget.title),
Expand All @@ -76,13 +60,13 @@ class _SaveFileAsState extends State<SaveFileAs> with FutureTaskStateMixin {
if (widget.local) {
final fs = await _selectLocalFileSource();
if (fs != null) {
widget.onSave(kdbxBloc!.saveAs(widget.file, fs));
widget.onSave(kdbxBloc.saveAs(widget.file, fs));
}
} else {
final result = await _saveAsCloudStorage(widget.cs!);
if (result != null) {
widget.onSave(
kdbxBloc!.saveAsNewFile(widget.file, result, widget.cs!));
kdbxBloc.saveAsNewFile(widget.file, result, widget.cs!));
}
}
},
Expand Down

0 comments on commit f16b3a8

Please sign in to comment.