From 910bdeae124e9d793e4d297773b1163db14485b8 Mon Sep 17 00:00:00 2001 From: mitchell Date: Sun, 7 Jul 2019 00:14:09 -0400 Subject: [PATCH] Add credential screen & screen arguments; refactor config screen --- lib/main.dart | 26 +++- lib/repositories/credentials_client.dart | 4 + lib/screens/authentication.dart | 6 +- lib/screens/config.dart | 47 ++++--- lib/screens/credential.dart | 155 +++++++++++++++++++++++ lib/screens/credentials.dart | 17 ++- lib/screens/home.dart | 8 +- lib/types/screen_arguments.dart | 8 ++ lib/widgets/text_field.dart | 6 + 9 files changed, 246 insertions(+), 31 deletions(-) create mode 100644 lib/screens/credential.dart create mode 100644 lib/types/screen_arguments.dart diff --git a/lib/main.dart b/lib/main.dart index b16f5a8..d3d417d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,11 +5,13 @@ import 'repositories/credentials_client.dart'; import 'repositories/config.dart' as repo; import 'screens/authentication.dart'; +import 'screens/credential.dart'; import 'screens/credentials.dart'; import 'screens/config.dart'; import 'screens/home.dart'; import 'types/abstracts.dart'; +import 'types/screen_arguments.dart'; void main() => runApp(Selfpass()); @@ -31,23 +33,37 @@ class Selfpass extends StatelessWidget { break; case '/home': - title = 'Credential Hosts'; + title = 'Hosts'; builder = (BuildContext context) => Provider( builder: (BuildContext context) => - CredentialsClient(settings.arguments), + CredentialsClient.cached(config: settings.arguments), child: Home(), ); break; case '/credentials': title = 'Credentials'; - builder = - (BuildContext context) => Credentials(settings.arguments); + builder = (BuildContext context) => Provider( + builder: (BuildContext context) => + CredentialsClient.cached(), + child: Credentials(settings.arguments), + ); + break; + + case '/credential': + title = 'Credential'; + builder = (BuildContext context) => Provider( + builder: (BuildContext context) => + CredentialsClient.cached(), + child: Credential(settings.arguments), + ); break; case '/config': + final ConfigScreenArguments arguments = settings.arguments; title = 'Configuration'; - builder = (BuildContext context) => Config(settings.arguments); + builder = (BuildContext context) => + Config(arguments.connectionConfig, arguments.privateKey); break; } diff --git a/lib/repositories/credentials_client.dart b/lib/repositories/credentials_client.dart index b84a757..a51de43 100644 --- a/lib/repositories/credentials_client.dart +++ b/lib/repositories/credentials_client.dart @@ -12,6 +12,7 @@ import '../types/connection_config.dart'; import '../types/credential.dart'; class CredentialsClient implements CredentialsRepo { + static CredentialsClient _cached; grpc.CredentialServiceClient _client; CredentialsClient(ConnectionConfig config) { @@ -32,6 +33,9 @@ class CredentialsClient implements CredentialsRepo { )); } + factory CredentialsClient.cached({ConnectionConfig config}) => + _cached == null ? _cached = CredentialsClient(config) : _cached; + Stream getAllMetadata(String sourceHost) { final request = grpc.GetAllMetadataRequest(); request.sourceHost = sourceHost; diff --git a/lib/screens/authentication.dart b/lib/screens/authentication.dart index a65fecf..cbf51c9 100644 --- a/lib/screens/authentication.dart +++ b/lib/screens/authentication.dart @@ -48,6 +48,7 @@ class _AuthenticationState extends State { const Flexible(child: Text('Master password:')), Flexible( child: TextField( + maxLines: 1, obscure: true, autofocus: true, controller: _passwordController, @@ -59,6 +60,7 @@ class _AuthenticationState extends State { children.add(const Flexible(child: Text('Re-enter password:'))); children.add(Flexible( child: TextField( + maxLines: 1, obscure: true, controller: _confirmController, ), @@ -85,7 +87,7 @@ class _AuthenticationState extends State { children.add(CupertinoButton( child: Text('Enter'), - onPressed: _buildSubmitPressedBuilder(context), + onPressed: _buildEnterPressedBuilder(context), )); if (_passesDontMatch) { @@ -99,7 +101,7 @@ class _AuthenticationState extends State { return children; } - VoidCallback _buildSubmitPressedBuilder(BuildContext context) { + VoidCallback _buildEnterPressedBuilder(BuildContext context) { return () async { if (await _passwordIsSet) { if (await _config.matchesPasswordHash(_passwordController.text)) { diff --git a/lib/screens/config.dart b/lib/screens/config.dart index c69fce5..58b7929 100644 --- a/lib/screens/config.dart +++ b/lib/screens/config.dart @@ -8,11 +8,13 @@ import '../widgets/text_field.dart'; class Config extends StatefulWidget { final ConnectionConfig connectionConfig; + final String privateKey; - const Config(this.connectionConfig, {Key key}) : super(key: key); + const Config(this.connectionConfig, this.privateKey, {Key key}) + : super(key: key); @override - State createState() => _ConfigState(this.connectionConfig); + State createState() => _ConfigState(this.connectionConfig, this.privateKey); } class _ConfigState extends State { @@ -20,10 +22,12 @@ class _ConfigState extends State { TextEditingController _caCertController; TextEditingController _certController; TextEditingController _privateCertController; + TextEditingController _privateKeyController; ConnectionConfig _connectionConfig; + String _privateKey; ConfigRepo _config; - _ConfigState(this._connectionConfig) { + _ConfigState(this._connectionConfig, this._privateKey) { if (_connectionConfig == null) { _connectionConfig = ConnectionConfig(); } @@ -35,6 +39,8 @@ class _ConfigState extends State { TextEditingController(text: _connectionConfig.caCertificate); _privateCertController = TextEditingController(text: _connectionConfig.privateCertificate); + + _privateKeyController = TextEditingController(text: _privateKey); } @override @@ -57,22 +63,26 @@ class _ConfigState extends State { ), ), child: Container( - margin: const EdgeInsets.symmetric(horizontal: 50.0), - child: Column(children: [ - Spacer(flex: 3), - Flexible(child: Text('Host:')), - Flexible(child: TextField(maxLines: 1, controller: _hostController)), - Flexible(child: Text('CA certificate:')), - Flexible( - child: TextField(maxLines: 3, controller: _caCertController)), - Flexible(child: Text('Client certificate:')), - Flexible(child: TextField(maxLines: 3, controller: _certController)), - Flexible(child: Text('Private certificate:')), - Flexible( - child: - TextField(maxLines: 3, controller: _privateCertController)), + margin: const EdgeInsets.symmetric(horizontal: 30), + child: ListView(children: [ + Container(margin: EdgeInsets.only(top: 10), child: Text('Host:')), + TextField(maxLines: 1, controller: _hostController), + Container( + margin: EdgeInsets.only(top: 5), child: Text('Private key:')), + TextField(maxLines: 1, controller: _privateKeyController), + Container( + margin: EdgeInsets.only(top: 5), child: Text('CA certificate:')), + TextField(maxLines: 5, controller: _caCertController), + Container( + margin: EdgeInsets.only(top: 5), + child: Text('Client certificate:')), + TextField(maxLines: 5, controller: _certController), + Container( + margin: EdgeInsets.only(top: 5), + child: Text('Private certificate:')), + TextField(maxLines: 5, controller: _privateCertController), CupertinoButton( - child: Text('Save'), onPressed: _makeSaveOnPressed(context)) + child: Text('Save'), onPressed: _makeSaveOnPressed(context)), ]), ), ); @@ -116,6 +126,7 @@ class _ConfigState extends State { ); await _config.setConnectionConfig(connConfig); + await _config.setPrivateKey(_privateKeyController.text); Navigator.of(context) .pushReplacementNamed('/home', arguments: connConfig); diff --git a/lib/screens/credential.dart b/lib/screens/credential.dart new file mode 100644 index 0000000..1d4db71 --- /dev/null +++ b/lib/screens/credential.dart @@ -0,0 +1,155 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; + +import '../types/credential.dart' as types; + +import '../widgets/text_field.dart'; + +class Credential extends StatefulWidget { + final types.Credential credential; + + const Credential(this.credential, {Key key}) : super(key: key); + + @override + State createState() => _CredentialState(credential); +} + +class _CredentialState extends State { + _CredentialControllers _controllers; + Map _fieldMap; + types.Credential _credential; + + _CredentialState(this._credential) : super() { + _controllers = _CredentialControllers.fromCredential(_credential); + _fieldMap = _buildFieldMap(_controllers, _credential); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar(), + child: Container( + margin: const EdgeInsets.only(top: 30, left: 30), + child: ListView( + children: _buildFieldRows(context), + ), + ), + ); + } + + Map _buildFieldMap( + _CredentialControllers controllers, + types.Credential credential, + ) { + final fieldMap = { + 'Id:': _FieldBuildConfig(mutable: false, text: credential.meta.id), + 'Created: ': _FieldBuildConfig( + mutable: false, + copyable: false, + text: credential.meta.createdAt.toString(), + ), + 'Updated: ': _FieldBuildConfig( + mutable: false, + copyable: false, + text: credential.meta.updatedAt.toString(), + ), + 'Host:': _FieldBuildConfig(controller: controllers.sourceHost), + 'Primary:': _FieldBuildConfig(controller: controllers.primary), + }; + + if (credential.meta.tag != null && credential.meta.tag != '') { + fieldMap['Tag'] = _FieldBuildConfig(controller: controllers.tag); + } + + if (credential.username != null && credential.username != '') { + fieldMap['User:'] = _FieldBuildConfig(controller: controllers.username); + } + + if (credential.email != null && credential.email != '') { + fieldMap['Email:'] = _FieldBuildConfig(controller: controllers.email); + } + + return fieldMap; + } + + List _buildFieldRows(BuildContext context) { + List rows = []; + + _fieldMap.forEach((key, value) { + rows.add(Container( + margin: EdgeInsets.only(top: 10), + child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)), + )); + + final List widgets = [ + Expanded( + flex: 3, + child: value.mutable + ? TextField(maxLines: 1, controller: value.controller) + : Container( + margin: EdgeInsets.symmetric(vertical: 10), + child: Text(value.text), + ), + ), + ]; + + if (value.copyable) { + widgets.add(Flexible( + child: CupertinoButton( + child: Text('Copy'), + onPressed: () => Clipboard.setData(ClipboardData( + text: value.mutable ? value.controller.text : value.text, + )), + ), + )); + } + + rows.add(Row(children: widgets)); + }); + + return rows; + } +} + +class _FieldBuildConfig { + final TextEditingController controller; + final String text; + final bool mutable; + final bool copyable; + + const _FieldBuildConfig({ + this.mutable = true, + this.copyable = true, + this.controller, + this.text, + }); +} + +class _CredentialControllers { + final TextEditingController sourceHost; + final TextEditingController primary; + final TextEditingController tag; + final TextEditingController username; + final TextEditingController email; + final TextEditingController password; + final TextEditingController otpSecret; + + const _CredentialControllers({ + this.sourceHost, + this.primary, + this.tag, + this.username, + this.email, + this.password, + this.otpSecret, + }); + + factory _CredentialControllers.fromCredential(types.Credential credential) => + _CredentialControllers( + sourceHost: TextEditingController(text: credential.meta.sourceHost), + primary: TextEditingController(text: credential.meta.primary), + tag: TextEditingController(text: credential.meta.tag), + username: TextEditingController(text: credential.username), + email: TextEditingController(text: credential.email), + ); +} diff --git a/lib/screens/credentials.dart b/lib/screens/credentials.dart index 1a6efa7..0c086ed 100644 --- a/lib/screens/credentials.dart +++ b/lib/screens/credentials.dart @@ -1,5 +1,7 @@ import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; +import '../types/abstracts.dart'; import '../types/credential.dart'; import '../widgets/tappable_text_list.dart'; @@ -18,12 +20,21 @@ class Credentials extends StatelessWidget { } Map _buildTappableText(BuildContext context) { - var handleOnTap = () {}; + var makeOnTapHandler = (String id) => () async { + final credential = + await Provider.of(context).get(id); + Navigator.of(context).pushNamed('/credential', arguments: credential); + }; Map tappableText = {}; - metadatas.forEach( - (Metadata metadata) => tappableText[metadata.id] = handleOnTap); + metadatas.forEach((Metadata metadata) { + var primary = metadata.primary; + if (metadata.tag != null && metadata.tag != '') { + primary += "-" + metadata.tag; + } + tappableText[primary] = makeOnTapHandler(metadata.id); + }); return tappableText; } diff --git a/lib/screens/home.dart b/lib/screens/home.dart index 619692f..06f63b9 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import '../types/abstracts.dart'; import '../types/credential.dart'; +import '../types/screen_arguments.dart'; import '../widgets/tappable_text_list.dart'; @@ -72,7 +73,7 @@ class _HomeState extends State { } } - final handleOnTap = (List metadatas) => () => + final handleOnTap = (List metadatas) => () async => Navigator.of(context).pushNamed('/credentials', arguments: metadatas); final Map tappableText = {}; @@ -88,7 +89,8 @@ class _HomeState extends State { } GestureTapCallback _makeConfigOnTapHandler(BuildContext context) { - return () async => Navigator.of(context) - .pushNamed('/config', arguments: await _config.connectionConfig); + return () async => Navigator.of(context).pushNamed('/config', + arguments: ConfigScreenArguments( + await _config.connectionConfig, await _config.privateKey)); } } diff --git a/lib/types/screen_arguments.dart b/lib/types/screen_arguments.dart new file mode 100644 index 0000000..3f841a2 --- /dev/null +++ b/lib/types/screen_arguments.dart @@ -0,0 +1,8 @@ +import 'connection_config.dart'; + +class ConfigScreenArguments { + final ConnectionConfig connectionConfig; + final String privateKey; + + const ConfigScreenArguments(this.connectionConfig, this.privateKey); +} diff --git a/lib/widgets/text_field.dart b/lib/widgets/text_field.dart index ed52fa1..aa18e30 100644 --- a/lib/widgets/text_field.dart +++ b/lib/widgets/text_field.dart @@ -7,6 +7,8 @@ typedef OnSubmittedBuilder = ValueChanged Function( class TextField extends StatelessWidget { final OnSubmittedBuilder onSubmittedBuilder; final TextEditingController controller; + final Widget prefix; + final Widget suffix; final bool obscure; final bool autofocus; final bool autocorrect; @@ -21,6 +23,8 @@ class TextField extends StatelessWidget { this.minLines, this.maxLines, this.autocorrect = false, + this.prefix, + this.suffix, }); @override @@ -43,6 +47,8 @@ class TextField extends StatelessWidget { autocorrect: autocorrect, minLines: minLines, maxLines: maxLines, + prefix: prefix, + suffix: suffix, ), ); }