mirror of https://github.com/mitchell/selfpass.git
Add generatePassword; refactor GestureDetectors to CupertinoButton;
refactor hidden classes fields; refactor repositories to one library
This commit is contained in:
parent
67744527cc
commit
5a42345c08
|
@ -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';
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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';
|
|
@ -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');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue