Skip to content

Commit

Permalink
File Create: Show password strength #143
Browse files Browse the repository at this point in the history
  • Loading branch information
hpoul committed Sep 14, 2020
1 parent 7695efc commit 6beafb2
Show file tree
Hide file tree
Showing 18 changed files with 250 additions and 31 deletions.
2 changes: 2 additions & 0 deletions authpass/.idea/dictionaries/herbert.xml

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

1 change: 1 addition & 0 deletions authpass/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Android: improve autofill input field detection for usernames.
* Improve locking with biometric storage.
(Files can now be closed/locked without removing saved master passwords)
* File Create: Show password strength #143
* Password generator: include accented characters in 'Umlauts'
* French translations.

Expand Down
10 changes: 10 additions & 0 deletions authpass/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,16 @@
}
}
},
"passwordScore": "Strength: {score} of 4",
"@passwordScore": {
"description": "choosing a master password - calculated password strength.",
"placeholders": {
"score": {
"description": "integer of the score.",
"example": "3"
}
}
},
"unexpectedError": "Unexpected Error: {error}",
"@unexpectedError": {
"placeholders": {
Expand Down
3 changes: 3 additions & 0 deletions authpass/lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,9 @@ abstract class AppLocalizations {
// preferences: dynamically load website icons help text.
String preferenceDynamicLoadIconsSubtitle(Object urlFieldName);

// choosing a master password - calculated password strength.
String passwordScore(Object score);

// No description provided in @unexpectedError
String unexpectedError(String error);
}
Expand Down
5 changes: 5 additions & 0 deletions authpass/lib/l10n/app_localizations_de.dart
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ class AppLocalizationsDe extends AppLocalizations {
return 'Will make http requests with the value in ${urlFieldName} field to load website icons.';
}

@override
String passwordScore(Object score) {
return 'Strength: ${score} of 4';
}

@override
String unexpectedError(String error) {
return 'Unerwarteter Fehler: ${error}';
Expand Down
5 changes: 5 additions & 0 deletions authpass/lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ class AppLocalizationsEn extends AppLocalizations {
return 'Will make http requests with the value in ${urlFieldName} field to load website icons.';
}

@override
String passwordScore(Object score) {
return 'Strength: ${score} of 4';
}

@override
String unexpectedError(String error) {
return 'Unexpected Error: ${error}';
Expand Down
5 changes: 5 additions & 0 deletions authpass/lib/l10n/app_localizations_es.dart
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ class AppLocalizationsEs extends AppLocalizations {
return 'Will make http requests with the value in ${urlFieldName} field to load website icons.';
}

@override
String passwordScore(Object score) {
return 'Strength: ${score} of 4';
}

@override
String unexpectedError(String error) {
return 'Unexpected Error: ${error}';
Expand Down
5 changes: 5 additions & 0 deletions authpass/lib/l10n/app_localizations_et.dart
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ class AppLocalizationsEt extends AppLocalizations {
return 'Will make http requests with the value in ${urlFieldName} field to load website icons.';
}

@override
String passwordScore(Object score) {
return 'Strength: ${score} of 4';
}

@override
String unexpectedError(String error) {
return 'Unexpected Error: ${error}';
Expand Down
5 changes: 5 additions & 0 deletions authpass/lib/l10n/app_localizations_fi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ class AppLocalizationsFi extends AppLocalizations {
return 'Will make http requests with the value in ${urlFieldName} field to load website icons.';
}

@override
String passwordScore(Object score) {
return 'Strength: ${score} of 4';
}

@override
String unexpectedError(String error) {
return 'Unexpected Error: ${error}';
Expand Down
5 changes: 5 additions & 0 deletions authpass/lib/l10n/app_localizations_fr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ class AppLocalizationsFr extends AppLocalizations {
return 'Will make http requests with the value in ${urlFieldName} field to load website icons.';
}

@override
String passwordScore(Object score) {
return 'Strength: ${score} of 4';
}

@override
String unexpectedError(String error) {
return 'Erreur inattendue : ${error}';
Expand Down
5 changes: 5 additions & 0 deletions authpass/lib/l10n/app_localizations_lt.dart
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ class AppLocalizationsLt extends AppLocalizations {
return 'Will make http requests with the value in ${urlFieldName} field to load website icons.';
}

@override
String passwordScore(Object score) {
return 'Strength: ${score} of 4';
}

@override
String unexpectedError(String error) {
return 'Nenumatyta klaida: ${error}';
Expand Down
5 changes: 5 additions & 0 deletions authpass/lib/l10n/app_localizations_ru.dart
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ class AppLocalizationsRu extends AppLocalizations {
return 'Will make http requests with the value in ${urlFieldName} field to load website icons.';
}

@override
String passwordScore(Object score) {
return 'Strength: ${score} of 4';
}

@override
String unexpectedError(String error) {
return 'Неожиданная ошибка: ${error}';
Expand Down
5 changes: 5 additions & 0 deletions authpass/lib/l10n/app_localizations_uk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,11 @@ class AppLocalizationsUk extends AppLocalizations {
return 'Will make http requests with the value in ${urlFieldName} field to load website icons.';
}

@override
String passwordScore(Object score) {
return 'Strength: ${score} of 4';
}

@override
String unexpectedError(String error) {
return 'Неочікувана помилка: ${error}';
Expand Down
4 changes: 1 addition & 3 deletions authpass/lib/ui/screens/cloud/cloud_mailbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:kdbx/kdbx.dart';
import 'package:provider/provider.dart';

import 'package:logging/logging.dart';
import 'package:provider/provider.dart';

final _logger = Logger('cloud_mailbox');

Expand Down
151 changes: 130 additions & 21 deletions authpass/lib/ui/screens/create_file.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import 'package:authpass/bloc/kdbx_bloc.dart';
import 'package:authpass/l10n/app_localizations.dart';
import 'package:authpass/ui/screens/main_app_scaffold.dart';
import 'package:authpass/ui/widgets/password_input_field.dart';
import 'package:authpass/ui/widgets/primary_button.dart';
import 'package:authpass/utils/dialog_utils.dart';
import 'package:authpass/utils/extension_methods.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_async_utils/flutter_async_utils.dart';
import 'package:provider/provider.dart';

import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:recase/recase.dart';
// ignore: implementation_imports
import 'package:zxcvbn/src/result.dart';
import 'package:zxcvbn/zxcvbn.dart';

final _logger = Logger('create_file');

Expand All @@ -24,11 +29,14 @@ class CreateFile extends StatefulWidget {
}

class _CreateFileState extends State<CreateFile> with FutureTaskStateMixin {
static final _zxcvbn = Zxcvbn();

final GlobalKey<FormState> _formKey = GlobalKey();
final TextEditingController _databaseName = TextEditingController();
final TextEditingController _password = TextEditingController();
final FocusNode _passwordFocus = FocusNode();
bool _passwordObscured = true;

Result _strength;

@override
void didChangeDependencies() {
Expand All @@ -52,6 +60,7 @@ class _CreateFileState extends State<CreateFile> with FutureTaskStateMixin {
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 4),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
controller: _databaseName,
Expand All @@ -68,40 +77,42 @@ class _CreateFileState extends State<CreateFile> with FutureTaskStateMixin {
}
return null;
},
autofocus: true,
),
const SizedBox(height: 16),
TextFormField(
PasswordInputField(
labelText: loc.masterPasswordHelpText,
controller: _password,
obscureText: _passwordObscured,
focusNode: _passwordFocus,
autofocus: true,
onFieldSubmitted: (val) => _submitCallback()(),
decoration: InputDecoration(
labelText: loc.masterPasswordHelpText,
filled: true,
suffix: InkWell(
child: _passwordObscured
? const Icon(Icons.lock)
: const Icon(Icons.lock_open),
onTap: () {
setState(() {
_passwordObscured = !_passwordObscured;
});
},
),
),
autovalidateMode: AutovalidateMode.onUserInteraction,
onChanged: (value) {
final userInput = _databaseName.text.pathCase.split('/');
setState(() {
if (value.isEmpty) {
_strength = null;
} else {
_strength =
_zxcvbn.evaluate(value, userInputs: userInput);
}
});
},
validator: (val) {
if (val.isEmpty) {
return loc.masterPasswordMissingCreate;
}
return null;
},
),
const SizedBox(height: 8),
PasswordStrengthDisplay(strength: _strength),
Container(
padding: const EdgeInsets.only(top: 8),
alignment: Alignment.centerRight,
child: task != null
? const CircularProgressIndicator()
? const CircularProgressIndicator(
backgroundColor: Colors.red,
)
: PrimaryButton(
large: false,
child: Text(loc.createDatabaseAction),
Expand Down Expand Up @@ -143,3 +154,101 @@ class _CreateFileState extends State<CreateFile> with FutureTaskStateMixin {
}
});
}

class PasswordStrengthDisplay extends ImplicitlyAnimatedWidget {
const PasswordStrengthDisplay({Key key, this.strength})
: super(key: key, duration: const Duration(milliseconds: 500));

final Result strength;

@override
_PasswordStrengthDisplayState createState() =>
_PasswordStrengthDisplayState();
}

class _PasswordStrengthDisplayState
extends AnimatedWidgetBaseState<PasswordStrengthDisplay> {
static final _strengthColors = [
Colors.redAccent,
Colors.orange,
Colors.yellow,
Colors.blueAccent,
Colors.lightGreenAccent
];

Tween<double> _scoreTween;
ColorTween _colorTween;
ColorTween _backgroundColorTween;

@override
void forEachTween(visitor) {
_scoreTween = visitor(
_scoreTween,
widget.strength?.score?.let((val) => val + 1) ?? 0.0,
(dynamic value) => Tween<double>(begin: value as double))
as Tween<double>;
_colorTween = visitor(
_colorTween,
_strengthColors[widget.strength?.score?.toInt() ?? 0],
(dynamic value) => ColorTween(begin: value as Color)) as ColorTween;
_backgroundColorTween = visitor(
_backgroundColorTween,
widget.strength == null ? Colors.transparent : Colors.grey,
(dynamic value) => ColorTween(begin: value as Color)) as ColorTween;
}

@override
Widget build(BuildContext context) {
final _strength = widget.strength;
final loc = AppLocalizations.of(context);
final theme = Theme.of(context);
final feedback = _strength?.feedback?.warning?.takeUnlessBlank() ??
// _strength?.feedback?.suggestions?.firstOrNull ??
''; // NON-NLS

return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_strength != null) ...[
LinearProgressIndicator(
value: _scoreTween.evaluate(animation) / 5.0,
valueColor: AlwaysStoppedAnimation(
_colorTween.evaluate(animation),
),
backgroundColor: _backgroundColorTween.evaluate(animation),
),
const SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
loc.passwordScore(_strength.score.toInt()),
style: theme.textTheme.caption,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
Text(
feedback,
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: theme.textTheme.caption,
),
],
),
] else ...[
LinearProgressIndicator(
value: _scoreTween.evaluate(animation) / 5.0,
valueColor: AlwaysStoppedAnimation(_colorTween.evaluate(animation)),
backgroundColor: _backgroundColorTween.evaluate(animation),
),
const SizedBox(height: 4),
const Text(' '), // NON-NLS
],
],
);
}
}

0 comments on commit 6beafb2

Please sign in to comment.