Add generatePassword; refactor GestureDetectors to CupertinoButton;

refactor hidden classes fields; refactor repositories to one library
This commit is contained in:
mitchell 2019-07-17 22:20:04 -04:00
parent 67744527cc
commit 5a42345c08
11 changed files with 293 additions and 204 deletions

View File

@ -1,8 +1,7 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'repositories/grpc_credentials_client.dart'; import 'repositories/repositories.dart';
import 'repositories/encrypted_shared_preferences.dart';
import 'screens/authentication.dart'; import 'screens/authentication.dart';
import 'screens/config.dart'; import 'screens/config.dart';

View File

@ -1,3 +1,5 @@
part of 'repositories.dart';
class ConfigBase { class ConfigBase {
static const keyPrivateKey = "private_key"; static const keyPrivateKey = "private_key";
static const keyConnectionConfig = "connection_config"; static const keyConnectionConfig = "connection_config";
@ -11,8 +13,6 @@ class ConfigBase {
return _password; return _password;
} }
set password(String password) => _password = password;
void checkIfPasswordMatched() { void checkIfPasswordMatched() {
if (passwordMatched) return; if (passwordMatched) return;
throw Exception('password not matched yet'); throw Exception('password not matched yet');

View File

@ -1,13 +1,4 @@
import 'dart:convert'; part of 'repositories.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'config_base.dart';
import '../types/abstracts.dart';
import '../types/connection_config.dart';
import '../utils/crypto.dart' as crypto;
class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo { class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
@override @override
@ -19,7 +10,7 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
if (cipherText == null) return null; if (cipherText == null) return null;
final configJson = crypto.decrypt(cipherText, password); final configJson = crypto.decrypt(cipherText, _password);
return ConnectionConfig.fromJson(json.decode(configJson)); return ConnectionConfig.fromJson(json.decode(configJson));
} }
@ -44,7 +35,7 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
password, password,
); );
if (passwordMatched) this.password = password; if (passwordMatched) _password = password;
return passwordMatched; return passwordMatched;
} }
@ -66,7 +57,7 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final cipherText = prefs.getString(ConfigBase.keyPrivateKey); final cipherText = prefs.getString(ConfigBase.keyPrivateKey);
return crypto.decrypt(cipherText, password); return crypto.decrypt(cipherText, _password);
} }
@override @override
@ -79,7 +70,7 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
prefs.setString( prefs.setString(
ConfigBase.keyConnectionConfig, ConfigBase.keyConnectionConfig,
crypto.encrypt(configJson, password), crypto.encrypt(configJson, _password),
); );
} }
@ -87,7 +78,7 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
Future<void> setPassword(String password) async { Future<void> setPassword(String password) async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
this.password = password; _password = password;
passwordMatched = true; passwordMatched = true;
prefs.setString(ConfigBase.keyPassword, crypto.hashPassword(password)); prefs.setString(ConfigBase.keyPassword, crypto.hashPassword(password));
@ -97,6 +88,6 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
Future<void> setPrivateKey(String key) async { Future<void> setPrivateKey(String key) async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
prefs.setString(ConfigBase.keyPrivateKey, crypto.encrypt(key, password)); prefs.setString(ConfigBase.keyPrivateKey, crypto.encrypt(key, _password));
} }
} }

View File

@ -1,14 +1,4 @@
import 'dart:async'; part of 'repositories.dart';
import 'dart:convert';
import 'dart:io';
import 'package:grpc/grpc.dart';
import 'package:selfpass_protobuf/credentials.pbgrpc.dart' as grpc;
import 'package:selfpass_protobuf/credentials.pb.dart' as protobuf;
import '../types/abstracts.dart';
import '../types/connection_config.dart';
import '../types/credential.dart';
class GRPCCredentialsClient implements CredentialsRepo { class GRPCCredentialsClient implements CredentialsRepo {
static GRPCCredentialsClient _instance; static GRPCCredentialsClient _instance;

View File

@ -0,0 +1,20 @@
library repositories;
import 'dart:convert';
import 'dart:async';
import 'dart:io';
import 'package:grpc/grpc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:selfpass_protobuf/credentials.pbgrpc.dart' as grpc;
import 'package:selfpass_protobuf/credentials.pb.dart' as protobuf;
import '../types/abstracts.dart';
import '../types/connection_config.dart';
import '../types/credential.dart';
import '../utils/crypto.dart' as crypto;
part 'config_base.dart';
part 'encrypted_shared_preferences.dart';
part 'grpc_credentials_client.dart';

View File

@ -11,18 +11,18 @@ class Authentication extends StatefulWidget {
} }
class _AuthenticationState extends State<Authentication> { class _AuthenticationState extends State<Authentication> {
final TextEditingController _passwordController = TextEditingController(); final TextEditingController passwordController = TextEditingController();
final TextEditingController _confirmController = TextEditingController(); final TextEditingController confirmController = TextEditingController();
bool _invalid = false; bool invalid = false;
bool _passesDontMatch = false; bool passesDontMatch = false;
ConfigRepo _config; ConfigRepo config;
Future<bool> _passwordIsSet; Future<bool> passwordIsSet;
@override @override
didChangeDependencies() { didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
_config = Provider.of<ConfigRepo>(context); config = Provider.of<ConfigRepo>(context);
_passwordIsSet = _config.passwordIsSet; passwordIsSet = config.passwordIsSet;
} }
@override @override
@ -31,18 +31,18 @@ class _AuthenticationState extends State<Authentication> {
child: Container( child: Container(
margin: const EdgeInsets.symmetric(horizontal: 50.0), margin: const EdgeInsets.symmetric(horizontal: 50.0),
child: FutureBuilder<bool>( child: FutureBuilder<bool>(
future: _passwordIsSet, future: passwordIsSet,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) => builder: (BuildContext context, AsyncSnapshot<bool> snapshot) =>
snapshot.connectionState == ConnectionState.done snapshot.connectionState == ConnectionState.done
? Column( ? Column(
children: _buildColumnChildren(context, snapshot.data)) children: buildColumnChildren(context, snapshot.data))
: Center(child: CupertinoActivityIndicator()), : Center(child: CupertinoActivityIndicator()),
), ),
), ),
); );
} }
List<Widget> _buildColumnChildren(BuildContext context, bool passwordIsSet) { List<Widget> buildColumnChildren(BuildContext context, bool passwordIsSet) {
List<Widget> children = [ List<Widget> children = [
const Spacer(flex: 4), const Spacer(flex: 4),
const Flexible(child: Text('Master password:')), const Flexible(child: Text('Master password:')),
@ -51,7 +51,7 @@ class _AuthenticationState extends State<Authentication> {
maxLines: 1, maxLines: 1,
obscure: true, obscure: true,
autofocus: true, autofocus: true,
controller: _passwordController, controller: passwordController,
), ),
), ),
]; ];
@ -62,12 +62,12 @@ class _AuthenticationState extends State<Authentication> {
child: TextField( child: TextField(
maxLines: 1, maxLines: 1,
obscure: true, obscure: true,
controller: _confirmController, controller: confirmController,
), ),
)); ));
} }
if (_invalid) { if (invalid) {
children.add(const Flexible( children.add(const Flexible(
child: Text( child: Text(
'invalid masterpass', 'invalid masterpass',
@ -76,7 +76,7 @@ class _AuthenticationState extends State<Authentication> {
)); ));
} }
if (_passesDontMatch) { if (passesDontMatch) {
children.add(const Flexible( children.add(const Flexible(
child: Text( child: Text(
'passwords don\'t match', 'passwords don\'t match',
@ -85,14 +85,17 @@ class _AuthenticationState extends State<Authentication> {
)); ));
} }
children.add(CupertinoButton( children.add(Container(
padding: EdgeInsets.only(top: 20),
child: CupertinoButton.filled(
child: Text('Enter'), child: Text('Enter'),
onPressed: _buildEnterPressedBuilder(context), onPressed: buildEnterPressedBuilder(context),
),
)); ));
if (_passesDontMatch) { if (passesDontMatch) {
children.add(const Spacer(flex: 1)); children.add(const Spacer(flex: 1));
} else if (_invalid || !passwordIsSet) { } else if (invalid || !passwordIsSet) {
children.add(const Spacer(flex: 2)); children.add(const Spacer(flex: 2));
} else { } else {
children.add(const Spacer(flex: 3)); children.add(const Spacer(flex: 3));
@ -101,28 +104,28 @@ class _AuthenticationState extends State<Authentication> {
return children; return children;
} }
VoidCallback _buildEnterPressedBuilder(BuildContext context) { VoidCallback buildEnterPressedBuilder(BuildContext context) {
return () async { return () async {
if (await _passwordIsSet) { if (await passwordIsSet) {
if (await _config.matchesPasswordHash(_passwordController.text)) { if (await config.matchesPasswordHash(passwordController.text)) {
Navigator.of(context).pushReplacementNamed('/home', Navigator.of(context).pushReplacementNamed('/home',
arguments: await _config.connectionConfig); arguments: await config.connectionConfig);
return; return;
} }
this.setState(() => _invalid = true); this.setState(() => invalid = true);
return; return;
} }
if (_passwordController.text != _confirmController.text) { if (passwordController.text != confirmController.text) {
this.setState(() { this.setState(() {
_passesDontMatch = true; passesDontMatch = true;
}); });
return; return;
} }
_config.setPassword(_passwordController.text); config.setPassword(passwordController.text);
_passwordIsSet = Future<bool>.value(true); passwordIsSet = Future<bool>.value(true);
Navigator.of(context).pushReplacementNamed('/config'); Navigator.of(context).pushReplacementNamed('/config');
}; };
} }

View File

@ -18,98 +18,106 @@ class Config extends StatefulWidget {
} }
class _ConfigState extends State<Config> { class _ConfigState extends State<Config> {
TextEditingController _hostController; TextEditingController hostController;
TextEditingController _caCertController; TextEditingController caCertController;
TextEditingController _certController; TextEditingController certController;
TextEditingController _privateCertController; TextEditingController privateCertController;
TextEditingController _privateKeyController; TextEditingController privateKeyController;
ConnectionConfig _connectionConfig; ConnectionConfig connectionConfig;
String _privateKey; String privateKey;
ConfigRepo _config; ConfigRepo config;
_ConfigState(this._connectionConfig, this._privateKey) { _ConfigState(this.connectionConfig, this.privateKey) {
if (_connectionConfig == null) { if (connectionConfig == null) {
_connectionConfig = ConnectionConfig(); connectionConfig = ConnectionConfig();
} }
_hostController = TextEditingController(text: _connectionConfig.host); hostController = TextEditingController(text: connectionConfig.host);
_certController = certController = TextEditingController(text: connectionConfig.certificate);
TextEditingController(text: _connectionConfig.certificate); caCertController =
_caCertController = TextEditingController(text: connectionConfig.caCertificate);
TextEditingController(text: _connectionConfig.caCertificate); privateCertController =
_privateCertController = TextEditingController(text: connectionConfig.privateCertificate);
TextEditingController(text: _connectionConfig.privateCertificate);
_privateKeyController = TextEditingController(text: _privateKey); privateKeyController = TextEditingController(text: privateKey);
} }
@override @override
didChangeDependencies() async { didChangeDependencies() async {
super.didChangeDependencies(); super.didChangeDependencies();
_config = Provider.of<ConfigRepo>(context); config = Provider.of<ConfigRepo>(context);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoPageScaffold( return CupertinoPageScaffold(
navigationBar: _connectionConfig.host == null navigationBar: CupertinoNavigationBar(
trailing: connectionConfig?.host == null
? null ? null
: CupertinoNavigationBar( : CupertinoButton(
trailing: GestureDetector(
onTap: _buildResetAllHandler(context),
child: Text( child: Text(
'Reset', 'Reset',
style: TextStyle(color: CupertinoColors.destructiveRed), style: TextStyle(color: CupertinoColors.destructiveRed),
), ),
onPressed: buildResetAllHandler(context),
padding: EdgeInsets.zero,
), ),
), ),
child: Container( child: Container(
margin: const EdgeInsets.symmetric(horizontal: 30), margin: const EdgeInsets.symmetric(horizontal: 30),
child: ListView(children: [ child: ListView(children: [
Container(margin: EdgeInsets.only(top: 10), child: Text('Host:')), Container(margin: EdgeInsets.only(top: 10), child: Text('Host:')),
TextField(maxLines: 1, controller: _hostController), TextField(maxLines: 1, controller: hostController),
Container( Container(
margin: EdgeInsets.only(top: 5), child: Text('Private key:')), margin: EdgeInsets.only(top: 5), child: Text('Private key:')),
TextField(maxLines: 1, controller: _privateKeyController), TextField(maxLines: 1, controller: privateKeyController),
Container( Container(
margin: EdgeInsets.only(top: 5), child: Text('CA certificate:')), margin: EdgeInsets.only(top: 5), child: Text('CA certificate:')),
TextField(maxLines: 5, controller: _caCertController), TextField(maxLines: 5, controller: caCertController),
Container( Container(
margin: EdgeInsets.only(top: 5), margin: EdgeInsets.only(top: 5),
child: Text('Client certificate:')), child: Text('Client certificate:')),
TextField(maxLines: 5, controller: _certController), TextField(maxLines: 5, controller: certController),
Container( Container(
margin: EdgeInsets.only(top: 5), margin: EdgeInsets.only(top: 5),
child: Text('Private certificate:')), child: Text('Private certificate:')),
TextField(maxLines: 5, controller: _privateCertController), TextField(maxLines: 5, controller: privateCertController),
CupertinoButton( Container(
child: Text('Save'), onPressed: _makeSaveOnPressed(context)), padding: EdgeInsets.symmetric(vertical: 20),
margin: EdgeInsets.symmetric(horizontal: 70),
child: CupertinoButton.filled(
child: Text('Save'),
onPressed: makeSaveOnPressed(context),
),
),
]), ]),
), ),
); );
} }
GestureTapCallback _buildResetAllHandler(BuildContext context) { GestureTapCallback buildResetAllHandler(BuildContext context) {
return () { return () {
showCupertinoDialog( showCupertinoDialog(
context: context, context: context,
builder: (BuildContext context) => CupertinoAlertDialog( builder: (BuildContext context) => CupertinoAlertDialog(
content: Text('Are you sure?'), content: Text(
'Are you sure you want to delete all config values and lock the app?',
),
actions: [ actions: [
CupertinoDialogAction( CupertinoDialogAction(
isDefaultAction: true, isDefaultAction: true,
child: Text('Cancel'), child: Text('No'),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
CupertinoDialogAction( CupertinoDialogAction(
isDestructiveAction: true, isDestructiveAction: true,
child: Text('Confirm'), child: Text('Yes'),
onPressed: () async { onPressed: () async {
_connectionConfig = null; connectionConfig = null;
await _config.deleteAll(); await config.deleteAll();
Navigator.of(context) Navigator.of(context)
.pushNamedAndRemoveUntil('/', ModalRoute.withName('/')); .pushNamedAndRemoveUntil('/', ModalRoute.withName('/home'));
}, },
), ),
], ],
@ -118,17 +126,17 @@ class _ConfigState extends State<Config> {
}; };
} }
VoidCallback _makeSaveOnPressed(BuildContext context) { VoidCallback makeSaveOnPressed(BuildContext context) {
return () async { return () async {
final connConfig = ConnectionConfig( final connConfig = ConnectionConfig(
host: _hostController.text, host: hostController.text,
certificate: _certController.text, certificate: certController.text,
caCertificate: _caCertController.text, caCertificate: caCertController.text,
privateCertificate: _privateCertController.text, privateCertificate: privateCertController.text,
); );
await _config.setConnectionConfig(connConfig); await config.setConnectionConfig(connConfig);
await _config.setPrivateKey(_privateKeyController.text); await config.setPrivateKey(privateKeyController.text);
Navigator.of(context) Navigator.of(context)
.pushReplacementNamed('/home', arguments: connConfig); .pushReplacementNamed('/home', arguments: connConfig);

View File

@ -1,7 +1,9 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:otp/otp.dart'; import 'package:otp/otp.dart';
import 'package:provider/provider.dart';
import '../types/abstracts.dart';
import '../types/credential.dart' as types; import '../types/credential.dart' as types;
import '../widgets/text_field.dart'; import '../widgets/text_field.dart';
@ -16,26 +18,122 @@ class Credential extends StatefulWidget {
} }
class _CredentialState extends State<Credential> { class _CredentialState extends State<Credential> {
_CredentialControllers _controllers; _CredentialControllers controllers;
Map<String, _FieldBuildConfig> _fieldMap; Map<String, _FieldBuildConfig> fieldMap;
types.Credential _credential; types.Credential credential;
CredentialsRepo client;
_CredentialState(this._credential) : super(); _CredentialState(this.credential) : super() {
controllers = _CredentialControllers.fromCredential(credential);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
client = Provider.of<CredentialsRepo>(context);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoPageScaffold( return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(), navigationBar: CupertinoNavigationBar(
trailing: CupertinoButton(
child: Text(
'Delete',
style: TextStyle(color: CupertinoColors.destructiveRed),
),
onPressed: makeDeleteHandler(context),
padding: EdgeInsets.zero,
),
),
child: Container( child: Container(
padding: const EdgeInsets.only(top: 15, bottom: 30, left: 30), padding: const EdgeInsets.only(top: 15, bottom: 30, left: 30),
child: ListView( child: ListView(
children: _buildFieldRows(context), children: buildFieldRows(context),
), ),
), ),
); );
} }
Map<String, _FieldBuildConfig> _buildFieldMap( Function makeDeleteHandler(BuildContext context) {
return () {
showCupertinoDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
content: Text('Are you sure you want to delete this credential?'),
actions: <Widget>[
CupertinoDialogAction(
child: Text('No'),
isDefaultAction: true,
onPressed: () {
Navigator.of(context).pop();
},
),
CupertinoDialogAction(
child: Text('Yes'),
isDestructiveAction: true,
onPressed: () async {
await client.delete(credential.meta.id);
Navigator.of(context).pushNamedAndRemoveUntil(
'/home',
ModalRoute.withName('/home'),
);
},
),
],
),
);
};
}
List<Widget> buildFieldRows(BuildContext context) {
List<Widget> rows = [];
fieldMap = buildFieldMap(controllers, credential);
fieldMap.forEach((String prefix, _FieldBuildConfig config) {
rows.add(Container(
margin: EdgeInsets.only(top: 2.5),
child: Text(prefix, style: TextStyle(fontWeight: FontWeight.w600)),
));
final List<Widget> widgets = [
Expanded(
flex: 3,
child: config.mutable
? TextField(
maxLines: 1,
controller: config.controller,
obscure: config.obscured)
: Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Text(config.text)),
),
];
if (config.copyable) {
widgets.add(Expanded(
child: CupertinoButton(
child: Text(config.otp ? 'OTP' : 'Copy'),
onPressed: () => Clipboard.setData(ClipboardData(
text: config.otp
? OTP
.generateTOTPCode(config.controller.text,
DateTime.now().millisecondsSinceEpoch)
.toString()
: config.mutable ? config.controller.text : config.text,
)),
),
));
}
rows.add(Row(children: widgets));
});
return rows;
}
Map<String, _FieldBuildConfig> buildFieldMap(
_CredentialControllers controllers, _CredentialControllers controllers,
types.Credential credential, types.Credential credential,
) { ) {
@ -55,76 +153,28 @@ class _CredentialState extends State<Credential> {
'Primary:': _FieldBuildConfig(controller: controllers.primary), 'Primary:': _FieldBuildConfig(controller: controllers.primary),
}; };
if (credential.meta.tag != null && credential.meta.tag != '') { if (credential.meta.tag?.isNotEmpty ?? false) {
fieldMap['Tag'] = _FieldBuildConfig(controller: controllers.tag); fieldMap['Tag'] = _FieldBuildConfig(controller: controllers.tag);
} }
if (credential.username != null && credential.username != '') { if (credential.username?.isNotEmpty ?? false) {
fieldMap['User:'] = _FieldBuildConfig(controller: controllers.username); fieldMap['User:'] = _FieldBuildConfig(controller: controllers.username);
} }
if (credential.email != null && credential.email != '') { if (credential.email?.isNotEmpty ?? false) {
fieldMap['Email:'] = _FieldBuildConfig(controller: controllers.email); fieldMap['Email:'] = _FieldBuildConfig(controller: controllers.email);
} }
fieldMap['Password:'] = fieldMap['Password:'] =
_FieldBuildConfig(controller: controllers.password, obscured: true); _FieldBuildConfig(controller: controllers.password, obscured: true);
if (credential.otpSecret != null && credential.otpSecret != '') { if (credential.otpSecret?.isNotEmpty ?? false) {
fieldMap['OTP Secret:'] = _FieldBuildConfig( fieldMap['OTP Key:'] = _FieldBuildConfig(
controller: controllers.otpSecret, obscured: true, otp: true); controller: controllers.otpSecret, obscured: true, otp: true);
} }
return fieldMap; return fieldMap;
} }
List<Widget> _buildFieldRows(BuildContext context) {
List<Widget> rows = [];
_controllers = _CredentialControllers.fromCredential(_credential);
_fieldMap = _buildFieldMap(_controllers, _credential);
_fieldMap.forEach((key, value) {
rows.add(Container(
margin: EdgeInsets.only(top: 2.5),
child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)),
));
final List<Widget> widgets = [
Expanded(
flex: 3,
child: value.mutable
? TextField(
maxLines: 1,
controller: value.controller,
obscure: value.obscured)
: Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Text(value.text)),
),
];
if (value.copyable) {
widgets.add(Expanded(
child: CupertinoButton(
child: Text(value.otp ? 'OTP' : 'Copy'),
onPressed: () => Clipboard.setData(ClipboardData(
text: value.otp
? OTP
.generateTOTPCode(value.controller.text,
DateTime.now().millisecondsSinceEpoch)
.toString()
: value.mutable ? value.controller.text : value.text,
)),
),
));
}
rows.add(Row(children: widgets));
});
return rows;
}
} }
class _FieldBuildConfig { class _FieldBuildConfig {

View File

@ -17,11 +17,11 @@ class Home extends StatefulWidget {
} }
class _HomeState extends State<Home> with WidgetsBindingObserver { class _HomeState extends State<Home> with WidgetsBindingObserver {
CredentialsRepo _client; CredentialsRepo client;
ConfigRepo _config; ConfigRepo config;
Future<List<Metadata>> _metadatas; Future<List<Metadata>> metadatas;
bool _stateIsPaused = false; bool stateIsPaused = false;
Timer _pausedStateTimer; Timer pausedStateTimer;
@override @override
void initState() { void initState() {
@ -33,49 +33,51 @@ class _HomeState extends State<Home> with WidgetsBindingObserver {
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
_config = Provider.of<ConfigRepo>(context); config = Provider.of<ConfigRepo>(context);
_client = Provider.of<CredentialsRepo>(context); client = Provider.of<CredentialsRepo>(context);
_metadatas = _client.getAllMetadata('').toList(); metadatas = client.getAllMetadata('').toList();
} }
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
_stateIsPaused = state == AppLifecycleState.paused; stateIsPaused = state == AppLifecycleState.paused;
if (_stateIsPaused) { if (stateIsPaused) {
_pausedStateTimer = _newPausedStateTimer(); pausedStateTimer = newPausedStateTimer();
return; return;
} }
if (_pausedStateTimer != null) _pausedStateTimer.cancel(); if (pausedStateTimer != null) pausedStateTimer.cancel();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoPageScaffold( return CupertinoPageScaffold(
child: FutureBuilder<List<Metadata>>( child: FutureBuilder<List<Metadata>>(
future: _metadatas, future: metadatas,
builder: ( builder: (
BuildContext context, BuildContext context,
AsyncSnapshot<List<Metadata>> snapshot, AsyncSnapshot<List<Metadata>> snapshot,
) => ) =>
(snapshot.connectionState == ConnectionState.done) (snapshot.connectionState == ConnectionState.done)
? TappableTextList( ? TappableTextList(
tappableText: _buildTappableText(context, snapshot.data)) tappableText: buildTappableText(context, snapshot.data))
: Center(child: CupertinoActivityIndicator()), : Center(child: CupertinoActivityIndicator()),
), ),
navigationBar: CupertinoNavigationBar( navigationBar: CupertinoNavigationBar(
leading: GestureDetector( leading: CupertinoButton(
child: Align( child: Text(
child: Text('Lock', 'Lock',
style: TextStyle(color: CupertinoColors.destructiveRed)), style: TextStyle(color: CupertinoColors.destructiveRed),
alignment: Alignment(-0.9, 0)),
onTap: _makeLockOnTapHandler(context),
), ),
trailing: GestureDetector( onPressed: makeLockOnTapHandler(context),
padding: EdgeInsets.zero,
),
trailing: CupertinoButton(
child: Icon(CupertinoIcons.gear), child: Icon(CupertinoIcons.gear),
onTap: _makeConfigOnTapHandler(context), onPressed: makeConfigOnTapHandler(context),
padding: EdgeInsets.zero,
), ),
), ),
); );
@ -84,21 +86,21 @@ class _HomeState extends State<Home> with WidgetsBindingObserver {
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
if (_pausedStateTimer != null) _pausedStateTimer.cancel(); if (pausedStateTimer != null) pausedStateTimer.cancel();
super.dispose(); super.dispose();
} }
Timer _newPausedStateTimer() { Timer newPausedStateTimer() {
const checkPeriod = 30; const checkPeriod = 30;
return Timer(Duration(seconds: checkPeriod), () { return Timer(Duration(seconds: checkPeriod), () {
_config.reset(); config.reset();
Navigator.of(context) Navigator.of(context)
.pushNamedAndRemoveUntil('/', ModalRoute.withName('/home')); .pushNamedAndRemoveUntil('/', ModalRoute.withName('/home'));
}); });
} }
Map<String, GestureTapCallback> _buildTappableText( Map<String, GestureTapCallback> buildTappableText(
BuildContext context, BuildContext context,
List<Metadata> metadatas, List<Metadata> metadatas,
) { ) {
@ -127,18 +129,18 @@ class _HomeState extends State<Home> with WidgetsBindingObserver {
return tappableText; return tappableText;
} }
GestureTapCallback _makeLockOnTapHandler(BuildContext context) { GestureTapCallback makeLockOnTapHandler(BuildContext context) {
return () { return () {
_config.reset(); config.reset();
Navigator.of(context) Navigator.of(context)
.pushNamedAndRemoveUntil('/', ModalRoute.withName('/home')); .pushNamedAndRemoveUntil('/', ModalRoute.withName('/home'));
}; };
} }
GestureTapCallback _makeConfigOnTapHandler(BuildContext context) { GestureTapCallback makeConfigOnTapHandler(BuildContext context) {
return () async => Navigator.of(context).pushNamed('/config', return () async => Navigator.of(context).pushNamed('/config',
arguments: ConfigScreenArguments( arguments: ConfigScreenArguments(
connectionConfig: await _config.connectionConfig, connectionConfig: await config.connectionConfig,
privateKey: await _config.privateKey)); privateKey: await config.privateKey));
} }
} }

View File

@ -56,11 +56,13 @@ String encrypt(String plainText, String masterpass, [String privateKey]) {
); );
final random = Random.secure(); final random = Random.secure();
final ivBytes = List<int>.generate(aesBlockSize, (_) => random.nextInt(byteIntMax)); final ivBytes =
List<int>.generate(aesBlockSize, (_) => random.nextInt(byteIntMax));
final iv = IV(Uint8List.fromList(ivBytes)); final iv = IV(Uint8List.fromList(ivBytes));
final encrypter = Encrypter(AES(Key(key), mode: AESMode.cbc)); final encrypter = Encrypter(AES(Key(key), mode: AESMode.cbc));
final cipherBytes = List<int>.from(encrypter.encrypt(plainText, iv: iv).bytes); final cipherBytes =
List<int>.from(encrypter.encrypt(plainText, iv: iv).bytes);
cipherBytes.insertAll(0, ivBytes); cipherBytes.insertAll(0, ivBytes);
if (privateKeyWasEmpty) { if (privateKeyWasEmpty) {
@ -72,6 +74,30 @@ String encrypt(String plainText, String masterpass, [String privateKey]) {
return base64.encode(cipherBytes); return base64.encode(cipherBytes);
} }
String generatePassword(int length,
[bool numbers = true, bool specials = true]) {
const alphaValues = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const numberValues = '1234567890';
const specialValues = '!@#\$%^&*()-_=+';
final random = Random.secure();
List<int> values;
if (numbers && specials) {
values = (alphaValues + numberValues + specialValues).codeUnits;
} else if (numbers) {
values = (alphaValues + numberValues).codeUnits;
} else if (specials) {
values = (alphaValues + specialValues).codeUnits;
}
final valuesLen = values.length;
final passValues =
List<int>.generate(length, (_) => values[random.nextInt(valuesLen)]);
return String.fromCharCodes(passValues);
}
const saltSize = 16; const saltSize = 16;
const pbkdf2Rounds = 4096; const pbkdf2Rounds = 4096;
const keySize = 32; const keySize = 32;

View File

@ -35,7 +35,7 @@ packages:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.5" version: "1.0.4"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -108,7 +108,7 @@ packages:
name: googleapis_auth name: googleapis_auth
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.8" version: "0.2.10"
grpc: grpc:
dependency: "direct main" dependency: "direct main"
description: description:
@ -178,7 +178,7 @@ packages:
name: pedantic name: pedantic
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0+1" version: "1.7.0"
pointycastle: pointycastle:
dependency: transitive dependency: transitive
description: description:
@ -192,7 +192,7 @@ packages:
name: protobuf name: protobuf
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.13.12" version: "0.13.15"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description: