mirror of https://github.com/mitchell/selfpass.git
Add credential screen & screen arguments; refactor config screen
This commit is contained in:
parent
af8834f7bd
commit
910bdeae12
|
@ -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<CredentialsRepo>(
|
||||
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<CredentialsRepo>(
|
||||
builder: (BuildContext context) =>
|
||||
CredentialsClient.cached(),
|
||||
child: Credentials(settings.arguments),
|
||||
);
|
||||
break;
|
||||
|
||||
case '/credential':
|
||||
title = 'Credential';
|
||||
builder = (BuildContext context) => Provider<CredentialsRepo>(
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Metadata> getAllMetadata(String sourceHost) {
|
||||
final request = grpc.GetAllMetadataRequest();
|
||||
request.sourceHost = sourceHost;
|
||||
|
|
|
@ -48,6 +48,7 @@ class _AuthenticationState extends State<Authentication> {
|
|||
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<Authentication> {
|
|||
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<Authentication> {
|
|||
|
||||
children.add(CupertinoButton(
|
||||
child: Text('Enter'),
|
||||
onPressed: _buildSubmitPressedBuilder(context),
|
||||
onPressed: _buildEnterPressedBuilder(context),
|
||||
));
|
||||
|
||||
if (_passesDontMatch) {
|
||||
|
@ -99,7 +101,7 @@ class _AuthenticationState extends State<Authentication> {
|
|||
return children;
|
||||
}
|
||||
|
||||
VoidCallback _buildSubmitPressedBuilder(BuildContext context) {
|
||||
VoidCallback _buildEnterPressedBuilder(BuildContext context) {
|
||||
return () async {
|
||||
if (await _passwordIsSet) {
|
||||
if (await _config.matchesPasswordHash(_passwordController.text)) {
|
||||
|
|
|
@ -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<Config> {
|
||||
|
@ -20,10 +22,12 @@ class _ConfigState extends State<Config> {
|
|||
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<Config> {
|
|||
TextEditingController(text: _connectionConfig.caCertificate);
|
||||
_privateCertController =
|
||||
TextEditingController(text: _connectionConfig.privateCertificate);
|
||||
|
||||
_privateKeyController = TextEditingController(text: _privateKey);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -57,22 +63,26 @@ class _ConfigState extends State<Config> {
|
|||
),
|
||||
),
|
||||
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<Config> {
|
|||
);
|
||||
|
||||
await _config.setConnectionConfig(connConfig);
|
||||
await _config.setPrivateKey(_privateKeyController.text);
|
||||
|
||||
Navigator.of(context)
|
||||
.pushReplacementNamed('/home', arguments: connConfig);
|
||||
|
|
|
@ -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<Credential> {
|
||||
_CredentialControllers _controllers;
|
||||
Map<String, _FieldBuildConfig> _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<String, _FieldBuildConfig> _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<Widget> _buildFieldRows(BuildContext context) {
|
||||
List<Widget> rows = [];
|
||||
|
||||
_fieldMap.forEach((key, value) {
|
||||
rows.add(Container(
|
||||
margin: EdgeInsets.only(top: 10),
|
||||
child: Text(key, style: TextStyle(fontWeight: FontWeight.w600)),
|
||||
));
|
||||
|
||||
final List<Widget> 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),
|
||||
);
|
||||
}
|
|
@ -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<String, GestureTapCallback> _buildTappableText(BuildContext context) {
|
||||
var handleOnTap = () {};
|
||||
var makeOnTapHandler = (String id) => () async {
|
||||
final credential =
|
||||
await Provider.of<CredentialsRepo>(context).get(id);
|
||||
Navigator.of(context).pushNamed('/credential', arguments: credential);
|
||||
};
|
||||
|
||||
Map<String, GestureTapCallback> 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;
|
||||
}
|
||||
|
|
|
@ -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<Home> {
|
|||
}
|
||||
}
|
||||
|
||||
final handleOnTap = (List<Metadata> metadatas) => () =>
|
||||
final handleOnTap = (List<Metadata> metadatas) => () async =>
|
||||
Navigator.of(context).pushNamed('/credentials', arguments: metadatas);
|
||||
|
||||
final Map<String, GestureTapCallback> tappableText = {};
|
||||
|
@ -88,7 +89,8 @@ class _HomeState extends State<Home> {
|
|||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import 'connection_config.dart';
|
||||
|
||||
class ConfigScreenArguments {
|
||||
final ConnectionConfig connectionConfig;
|
||||
final String privateKey;
|
||||
|
||||
const ConfigScreenArguments(this.connectionConfig, this.privateKey);
|
||||
}
|
|
@ -7,6 +7,8 @@ typedef OnSubmittedBuilder = ValueChanged<String> 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue