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:provider/provider.dart';
|
||||
|
||||
import 'repositories/grpc_credentials_client.dart';
|
||||
import 'repositories/encrypted_shared_preferences.dart';
|
||||
import 'repositories/repositories.dart';
|
||||
|
||||
import 'screens/authentication.dart';
|
||||
import 'screens/config.dart';
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
part of 'repositories.dart';
|
||||
|
||||
class ConfigBase {
|
||||
static const keyPrivateKey = "private_key";
|
||||
static const keyConnectionConfig = "connection_config";
|
||||
|
@ -11,8 +13,6 @@ class ConfigBase {
|
|||
return _password;
|
||||
}
|
||||
|
||||
set password(String password) => _password = password;
|
||||
|
||||
void checkIfPasswordMatched() {
|
||||
if (passwordMatched) return;
|
||||
throw Exception('password not matched yet');
|
||||
|
|
|
@ -1,13 +1,4 @@
|
|||
import 'dart:convert';
|
||||
|
||||
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;
|
||||
part of 'repositories.dart';
|
||||
|
||||
class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
|
||||
@override
|
||||
|
@ -19,7 +10,7 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
|
|||
|
||||
if (cipherText == null) return null;
|
||||
|
||||
final configJson = crypto.decrypt(cipherText, password);
|
||||
final configJson = crypto.decrypt(cipherText, _password);
|
||||
|
||||
return ConnectionConfig.fromJson(json.decode(configJson));
|
||||
}
|
||||
|
@ -44,7 +35,7 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
|
|||
password,
|
||||
);
|
||||
|
||||
if (passwordMatched) this.password = password;
|
||||
if (passwordMatched) _password = password;
|
||||
|
||||
return passwordMatched;
|
||||
}
|
||||
|
@ -66,7 +57,7 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
|
|||
final prefs = await SharedPreferences.getInstance();
|
||||
final cipherText = prefs.getString(ConfigBase.keyPrivateKey);
|
||||
|
||||
return crypto.decrypt(cipherText, password);
|
||||
return crypto.decrypt(cipherText, _password);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -79,7 +70,7 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
|
|||
|
||||
prefs.setString(
|
||||
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 {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
this.password = password;
|
||||
_password = password;
|
||||
passwordMatched = true;
|
||||
|
||||
prefs.setString(ConfigBase.keyPassword, crypto.hashPassword(password));
|
||||
|
@ -97,6 +88,6 @@ class EncryptedSharedPreferences extends ConfigBase implements ConfigRepo {
|
|||
Future<void> setPrivateKey(String key) async {
|
||||
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';
|
||||
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';
|
||||
part of 'repositories.dart';
|
||||
|
||||
class GRPCCredentialsClient implements CredentialsRepo {
|
||||
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> {
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
final TextEditingController _confirmController = TextEditingController();
|
||||
bool _invalid = false;
|
||||
bool _passesDontMatch = false;
|
||||
ConfigRepo _config;
|
||||
Future<bool> _passwordIsSet;
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
final TextEditingController confirmController = TextEditingController();
|
||||
bool invalid = false;
|
||||
bool passesDontMatch = false;
|
||||
ConfigRepo config;
|
||||
Future<bool> passwordIsSet;
|
||||
|
||||
@override
|
||||
didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_config = Provider.of<ConfigRepo>(context);
|
||||
_passwordIsSet = _config.passwordIsSet;
|
||||
config = Provider.of<ConfigRepo>(context);
|
||||
passwordIsSet = config.passwordIsSet;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -31,18 +31,18 @@ class _AuthenticationState extends State<Authentication> {
|
|||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 50.0),
|
||||
child: FutureBuilder<bool>(
|
||||
future: _passwordIsSet,
|
||||
future: passwordIsSet,
|
||||
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) =>
|
||||
snapshot.connectionState == ConnectionState.done
|
||||
? Column(
|
||||
children: _buildColumnChildren(context, snapshot.data))
|
||||
children: buildColumnChildren(context, snapshot.data))
|
||||
: Center(child: CupertinoActivityIndicator()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildColumnChildren(BuildContext context, bool passwordIsSet) {
|
||||
List<Widget> buildColumnChildren(BuildContext context, bool passwordIsSet) {
|
||||
List<Widget> children = [
|
||||
const Spacer(flex: 4),
|
||||
const Flexible(child: Text('Master password:')),
|
||||
|
@ -51,7 +51,7 @@ class _AuthenticationState extends State<Authentication> {
|
|||
maxLines: 1,
|
||||
obscure: true,
|
||||
autofocus: true,
|
||||
controller: _passwordController,
|
||||
controller: passwordController,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
@ -62,12 +62,12 @@ class _AuthenticationState extends State<Authentication> {
|
|||
child: TextField(
|
||||
maxLines: 1,
|
||||
obscure: true,
|
||||
controller: _confirmController,
|
||||
controller: confirmController,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if (_invalid) {
|
||||
if (invalid) {
|
||||
children.add(const Flexible(
|
||||
child: Text(
|
||||
'invalid masterpass',
|
||||
|
@ -76,7 +76,7 @@ class _AuthenticationState extends State<Authentication> {
|
|||
));
|
||||
}
|
||||
|
||||
if (_passesDontMatch) {
|
||||
if (passesDontMatch) {
|
||||
children.add(const Flexible(
|
||||
child: Text(
|
||||
'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'),
|
||||
onPressed: _buildEnterPressedBuilder(context),
|
||||
onPressed: buildEnterPressedBuilder(context),
|
||||
),
|
||||
));
|
||||
|
||||
if (_passesDontMatch) {
|
||||
if (passesDontMatch) {
|
||||
children.add(const Spacer(flex: 1));
|
||||
} else if (_invalid || !passwordIsSet) {
|
||||
} else if (invalid || !passwordIsSet) {
|
||||
children.add(const Spacer(flex: 2));
|
||||
} else {
|
||||
children.add(const Spacer(flex: 3));
|
||||
|
@ -101,28 +104,28 @@ class _AuthenticationState extends State<Authentication> {
|
|||
return children;
|
||||
}
|
||||
|
||||
VoidCallback _buildEnterPressedBuilder(BuildContext context) {
|
||||
VoidCallback buildEnterPressedBuilder(BuildContext context) {
|
||||
return () async {
|
||||
if (await _passwordIsSet) {
|
||||
if (await _config.matchesPasswordHash(_passwordController.text)) {
|
||||
if (await passwordIsSet) {
|
||||
if (await config.matchesPasswordHash(passwordController.text)) {
|
||||
Navigator.of(context).pushReplacementNamed('/home',
|
||||
arguments: await _config.connectionConfig);
|
||||
arguments: await config.connectionConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(() => _invalid = true);
|
||||
this.setState(() => invalid = true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_passwordController.text != _confirmController.text) {
|
||||
if (passwordController.text != confirmController.text) {
|
||||
this.setState(() {
|
||||
_passesDontMatch = true;
|
||||
passesDontMatch = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_config.setPassword(_passwordController.text);
|
||||
_passwordIsSet = Future<bool>.value(true);
|
||||
config.setPassword(passwordController.text);
|
||||
passwordIsSet = Future<bool>.value(true);
|
||||
Navigator.of(context).pushReplacementNamed('/config');
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,98 +18,106 @@ class Config extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _ConfigState extends State<Config> {
|
||||
TextEditingController _hostController;
|
||||
TextEditingController _caCertController;
|
||||
TextEditingController _certController;
|
||||
TextEditingController _privateCertController;
|
||||
TextEditingController _privateKeyController;
|
||||
ConnectionConfig _connectionConfig;
|
||||
String _privateKey;
|
||||
ConfigRepo _config;
|
||||
TextEditingController hostController;
|
||||
TextEditingController caCertController;
|
||||
TextEditingController certController;
|
||||
TextEditingController privateCertController;
|
||||
TextEditingController privateKeyController;
|
||||
ConnectionConfig connectionConfig;
|
||||
String privateKey;
|
||||
ConfigRepo config;
|
||||
|
||||
_ConfigState(this._connectionConfig, this._privateKey) {
|
||||
if (_connectionConfig == null) {
|
||||
_connectionConfig = ConnectionConfig();
|
||||
_ConfigState(this.connectionConfig, this.privateKey) {
|
||||
if (connectionConfig == null) {
|
||||
connectionConfig = ConnectionConfig();
|
||||
}
|
||||
|
||||
_hostController = TextEditingController(text: _connectionConfig.host);
|
||||
_certController =
|
||||
TextEditingController(text: _connectionConfig.certificate);
|
||||
_caCertController =
|
||||
TextEditingController(text: _connectionConfig.caCertificate);
|
||||
_privateCertController =
|
||||
TextEditingController(text: _connectionConfig.privateCertificate);
|
||||
hostController = TextEditingController(text: connectionConfig.host);
|
||||
certController = TextEditingController(text: connectionConfig.certificate);
|
||||
caCertController =
|
||||
TextEditingController(text: connectionConfig.caCertificate);
|
||||
privateCertController =
|
||||
TextEditingController(text: connectionConfig.privateCertificate);
|
||||
|
||||
_privateKeyController = TextEditingController(text: _privateKey);
|
||||
privateKeyController = TextEditingController(text: privateKey);
|
||||
}
|
||||
|
||||
@override
|
||||
didChangeDependencies() async {
|
||||
super.didChangeDependencies();
|
||||
|
||||
_config = Provider.of<ConfigRepo>(context);
|
||||
config = Provider.of<ConfigRepo>(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: _connectionConfig.host == null
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
trailing: connectionConfig?.host == null
|
||||
? null
|
||||
: CupertinoNavigationBar(
|
||||
trailing: GestureDetector(
|
||||
onTap: _buildResetAllHandler(context),
|
||||
: CupertinoButton(
|
||||
child: Text(
|
||||
'Reset',
|
||||
style: TextStyle(color: CupertinoColors.destructiveRed),
|
||||
),
|
||||
onPressed: buildResetAllHandler(context),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 30),
|
||||
child: ListView(children: [
|
||||
Container(margin: EdgeInsets.only(top: 10), child: Text('Host:')),
|
||||
TextField(maxLines: 1, controller: _hostController),
|
||||
TextField(maxLines: 1, controller: hostController),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 5), child: Text('Private key:')),
|
||||
TextField(maxLines: 1, controller: _privateKeyController),
|
||||
TextField(maxLines: 1, controller: privateKeyController),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 5), child: Text('CA certificate:')),
|
||||
TextField(maxLines: 5, controller: _caCertController),
|
||||
TextField(maxLines: 5, controller: caCertController),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 5),
|
||||
child: Text('Client certificate:')),
|
||||
TextField(maxLines: 5, controller: _certController),
|
||||
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)),
|
||||
TextField(maxLines: 5, controller: privateCertController),
|
||||
Container(
|
||||
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 () {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
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: [
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: Text('Cancel'),
|
||||
child: Text('No'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
isDestructiveAction: true,
|
||||
child: Text('Confirm'),
|
||||
child: Text('Yes'),
|
||||
onPressed: () async {
|
||||
_connectionConfig = null;
|
||||
await _config.deleteAll();
|
||||
connectionConfig = null;
|
||||
await config.deleteAll();
|
||||
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 {
|
||||
final connConfig = ConnectionConfig(
|
||||
host: _hostController.text,
|
||||
certificate: _certController.text,
|
||||
caCertificate: _caCertController.text,
|
||||
privateCertificate: _privateCertController.text,
|
||||
host: hostController.text,
|
||||
certificate: certController.text,
|
||||
caCertificate: caCertController.text,
|
||||
privateCertificate: privateCertController.text,
|
||||
);
|
||||
|
||||
await _config.setConnectionConfig(connConfig);
|
||||
await _config.setPrivateKey(_privateKeyController.text);
|
||||
await config.setConnectionConfig(connConfig);
|
||||
await config.setPrivateKey(privateKeyController.text);
|
||||
|
||||
Navigator.of(context)
|
||||
.pushReplacementNamed('/home', arguments: connConfig);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:otp/otp.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../types/abstracts.dart';
|
||||
import '../types/credential.dart' as types;
|
||||
|
||||
import '../widgets/text_field.dart';
|
||||
|
@ -16,26 +18,122 @@ class Credential extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _CredentialState extends State<Credential> {
|
||||
_CredentialControllers _controllers;
|
||||
Map<String, _FieldBuildConfig> _fieldMap;
|
||||
types.Credential _credential;
|
||||
_CredentialControllers controllers;
|
||||
Map<String, _FieldBuildConfig> fieldMap;
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(),
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
trailing: CupertinoButton(
|
||||
child: Text(
|
||||
'Delete',
|
||||
style: TextStyle(color: CupertinoColors.destructiveRed),
|
||||
),
|
||||
onPressed: makeDeleteHandler(context),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 15, bottom: 30, left: 30),
|
||||
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,
|
||||
types.Credential credential,
|
||||
) {
|
||||
|
@ -55,76 +153,28 @@ class _CredentialState extends State<Credential> {
|
|||
'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);
|
||||
}
|
||||
|
||||
if (credential.username != null && credential.username != '') {
|
||||
if (credential.username?.isNotEmpty ?? false) {
|
||||
fieldMap['User:'] = _FieldBuildConfig(controller: controllers.username);
|
||||
}
|
||||
|
||||
if (credential.email != null && credential.email != '') {
|
||||
if (credential.email?.isNotEmpty ?? false) {
|
||||
fieldMap['Email:'] = _FieldBuildConfig(controller: controllers.email);
|
||||
}
|
||||
|
||||
fieldMap['Password:'] =
|
||||
_FieldBuildConfig(controller: controllers.password, obscured: true);
|
||||
|
||||
if (credential.otpSecret != null && credential.otpSecret != '') {
|
||||
fieldMap['OTP Secret:'] = _FieldBuildConfig(
|
||||
if (credential.otpSecret?.isNotEmpty ?? false) {
|
||||
fieldMap['OTP Key:'] = _FieldBuildConfig(
|
||||
controller: controllers.otpSecret, obscured: true, otp: true);
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -17,11 +17,11 @@ class Home extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _HomeState extends State<Home> with WidgetsBindingObserver {
|
||||
CredentialsRepo _client;
|
||||
ConfigRepo _config;
|
||||
Future<List<Metadata>> _metadatas;
|
||||
bool _stateIsPaused = false;
|
||||
Timer _pausedStateTimer;
|
||||
CredentialsRepo client;
|
||||
ConfigRepo config;
|
||||
Future<List<Metadata>> metadatas;
|
||||
bool stateIsPaused = false;
|
||||
Timer pausedStateTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -33,49 +33,51 @@ class _HomeState extends State<Home> with WidgetsBindingObserver {
|
|||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
_config = Provider.of<ConfigRepo>(context);
|
||||
_client = Provider.of<CredentialsRepo>(context);
|
||||
config = Provider.of<ConfigRepo>(context);
|
||||
client = Provider.of<CredentialsRepo>(context);
|
||||
|
||||
_metadatas = _client.getAllMetadata('').toList();
|
||||
metadatas = client.getAllMetadata('').toList();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
_stateIsPaused = state == AppLifecycleState.paused;
|
||||
stateIsPaused = state == AppLifecycleState.paused;
|
||||
|
||||
if (_stateIsPaused) {
|
||||
_pausedStateTimer = _newPausedStateTimer();
|
||||
if (stateIsPaused) {
|
||||
pausedStateTimer = newPausedStateTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pausedStateTimer != null) _pausedStateTimer.cancel();
|
||||
if (pausedStateTimer != null) pausedStateTimer.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CupertinoPageScaffold(
|
||||
child: FutureBuilder<List<Metadata>>(
|
||||
future: _metadatas,
|
||||
future: metadatas,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
AsyncSnapshot<List<Metadata>> snapshot,
|
||||
) =>
|
||||
(snapshot.connectionState == ConnectionState.done)
|
||||
? TappableTextList(
|
||||
tappableText: _buildTappableText(context, snapshot.data))
|
||||
tappableText: buildTappableText(context, snapshot.data))
|
||||
: Center(child: CupertinoActivityIndicator()),
|
||||
),
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
leading: GestureDetector(
|
||||
child: Align(
|
||||
child: Text('Lock',
|
||||
style: TextStyle(color: CupertinoColors.destructiveRed)),
|
||||
alignment: Alignment(-0.9, 0)),
|
||||
onTap: _makeLockOnTapHandler(context),
|
||||
leading: CupertinoButton(
|
||||
child: Text(
|
||||
'Lock',
|
||||
style: TextStyle(color: CupertinoColors.destructiveRed),
|
||||
),
|
||||
trailing: GestureDetector(
|
||||
onPressed: makeLockOnTapHandler(context),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
trailing: CupertinoButton(
|
||||
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
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
if (_pausedStateTimer != null) _pausedStateTimer.cancel();
|
||||
if (pausedStateTimer != null) pausedStateTimer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Timer _newPausedStateTimer() {
|
||||
Timer newPausedStateTimer() {
|
||||
const checkPeriod = 30;
|
||||
|
||||
return Timer(Duration(seconds: checkPeriod), () {
|
||||
_config.reset();
|
||||
config.reset();
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil('/', ModalRoute.withName('/home'));
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, GestureTapCallback> _buildTappableText(
|
||||
Map<String, GestureTapCallback> buildTappableText(
|
||||
BuildContext context,
|
||||
List<Metadata> metadatas,
|
||||
) {
|
||||
|
@ -127,18 +129,18 @@ class _HomeState extends State<Home> with WidgetsBindingObserver {
|
|||
return tappableText;
|
||||
}
|
||||
|
||||
GestureTapCallback _makeLockOnTapHandler(BuildContext context) {
|
||||
GestureTapCallback makeLockOnTapHandler(BuildContext context) {
|
||||
return () {
|
||||
_config.reset();
|
||||
config.reset();
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil('/', ModalRoute.withName('/home'));
|
||||
};
|
||||
}
|
||||
|
||||
GestureTapCallback _makeConfigOnTapHandler(BuildContext context) {
|
||||
GestureTapCallback makeConfigOnTapHandler(BuildContext context) {
|
||||
return () async => Navigator.of(context).pushNamed('/config',
|
||||
arguments: ConfigScreenArguments(
|
||||
connectionConfig: await _config.connectionConfig,
|
||||
privateKey: await _config.privateKey));
|
||||
connectionConfig: await config.connectionConfig,
|
||||
privateKey: await config.privateKey));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,11 +56,13 @@ String encrypt(String plainText, String masterpass, [String privateKey]) {
|
|||
);
|
||||
|
||||
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 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);
|
||||
|
||||
if (privateKeyWasEmpty) {
|
||||
|
@ -72,6 +74,30 @@ String encrypt(String plainText, String masterpass, [String privateKey]) {
|
|||
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 pbkdf2Rounds = 4096;
|
||||
const keySize = 32;
|
||||
|
|
|
@ -35,7 +35,7 @@ packages:
|
|||
name: boolean_selector
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
version: "1.0.4"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -108,7 +108,7 @@ packages:
|
|||
name: googleapis_auth
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.8"
|
||||
version: "0.2.10"
|
||||
grpc:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -178,7 +178,7 @@ packages:
|
|||
name: pedantic
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0+1"
|
||||
version: "1.7.0"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -192,7 +192,7 @@ packages:
|
|||
name: protobuf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.13.12"
|
||||
version: "0.13.15"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
Loading…
Reference in New Issue